AdvDupe = {}

if (CLIENT) then return end

AdvDupe.Version = 1.836 .. " freelancer special"
AdvDupe.ToolVersion = 1.812
AdvDupe.FileVersion = 0.83

function AdvDupe.SaveDupeTablesToFile( pl, EntTables, ConstraintTables, HeadEntityIdx, HoldAngle, HoldPos, filename, desc, StartPos, debugsave )
	if (!AdvDupe[pl]) then AdvDupe[pl] = {} end
	local dir = AdvDupe[pl].cdir or AdvDupe.GetPlayersFolder(pl)
	local filename = dupeshare.ReplaceBadChar(filename)
	filename = dupeshare.FileNoOverWriteCheck( dir, filename )
	local temp = {}
	temp.HeadEntityIdx		= HeadEntityIdx
	temp.HoldAngle			= HoldAngle
	temp.HoldPos			= HoldPos
	temp.Entities			= EntTables
	ConstsTable				= {}
	for k, v in pairs(ConstraintTables) do
		table.insert( ConstsTable, v )
	end
	temp.Constraints		= ConstsTable
	
	local Creator			= pl:GetName()	or "unknown"
	local NumOfEnts			= table.Count(EntTables) or 0
	local NumOfConst		= table.Count(ConstraintTables) or 0
	
	local Header = {}
	Header[1] = "Type:"			.."AdvDupe File"
	Header[2] =	"Creator:"		..string.format('%q', Creator)
	Header[3] =	"Date:"			..os.date("%m/%d/%y")
	if (!desc) or (desc == "") then desc = "none" end
	Header[4] =	"Description:"	..string.format('%q', desc)
	Header[5] =	"Entities:"		..NumOfEnts
	Header[6] =	"Constraints:"	..NumOfConst
	
	local ExtraHeader = {}
	ExtraHeader[1] = "FileVersion:"				..AdvDupe.FileVersion
	ExtraHeader[2] = "AdvDupeVersion:"			..AdvDupe.Version
	ExtraHeader[3] = "AdvDupeToolVersion:"		..AdvDupe.ToolVersion
	ExtraHeader[4] = "AdvDupeSharedVersion:"	..dupeshare.Version
	ExtraHeader[5] = "SerialiserVersion:"		..Serialiser.Version
	ExtraHeader[6] = "WireVersion:"				..(WireVersion or "Not Installed")
	ExtraHeader[7] = "Time:"					..os.date("%I:%M %p")
	ExtraHeader[8] = "Head:"					..HeadEntityIdx
	ExtraHeader[9] = "HoldAngle:"				..string.format( "%g,%g,%g", HoldAngle.pitch, HoldAngle.yaw, HoldAngle.roll )
	ExtraHeader[10] = "HoldPos:"				..string.format( "%g,%g,%g", HoldPos.x, HoldPos.y, HoldPos.z )
	ExtraHeader[11] = "StartPos:"				..string.format( "%g,%g,%g", StartPos.x, StartPos.y, StartPos.z )
	
	Serialiser.SaveTablesToFile( pl, filename, Header, ExtraHeader, NumOfEnts, EntTables, NumOfConst, ConstsTable, debugsave )
	return filename, Creator, desc , NumOfEnts, NumOfConst, AdvDupe.FileVersion --for sending to client after saving
end

function AdvDupe.LoadDupeTableFromFile( pl, filepath )
	
	if ( !file.Exists(filepath) ) then return end
	local tool = AdvDupe.GetAdvDupeToolObj(pl)
	if ( !tool ) then return end
	local function Load1(pl, filepath, tool, temp)
		if ( string.Left(temp, 5) != "\"Out\"") then
			local function Load2NewFile(pl, filepath, tool, HeaderTbl, ExtraHeaderTbl, Data)
				if ( HeaderTbl.Type ) and ( HeaderTbl.Type == "AdvDupe File" ) then
					Msg("AdvDupe:Loaded new file "..filepath.."  version: "..ExtraHeaderTbl.FileVersion.."\n")
					ExtraHeaderTbl.FileVersion = tonumber(ExtraHeaderTbl.FileVersion)
					if (ExtraHeaderTbl.FileVersion > AdvDupe.FileVersion) then
						Msg("AdvDupeINFO:File is newer than installed version, failure may occure, you should update.")
					end
					if ( ExtraHeaderTbl.FileVersion >= 0.82 ) and ( ExtraHeaderTbl.FileVersion < 0.9 )then
						local a,b,c = ExtraHeaderTbl.HoldAngle:match("(.-),(.-),(.+)")
						local HoldAngle = Angle( tonumber(a), tonumber(b), tonumber(c) )
						local a,b,c = ExtraHeaderTbl.HoldPos:match("(.-),(.-),(.+)")
						local HoldPos = Vector( tonumber(a), tonumber(b), tonumber(c) )
						local StartPos
						if ( ExtraHeaderTbl.FileVersion >= 0.83 ) then
							local a,b,c = ExtraHeaderTbl.StartPos:match("(.-),(.-),(.+)")
							StartPos = Vector( tonumber(a), tonumber(b), tonumber(c) )
						end
						tool:LoadFileCallBack( filepath,
							Data.Entities, Data.Constraints,
							{},{}, tonumber(ExtraHeaderTbl.Head),
							HoldAngle,
							HoldPos, false,
							HeaderTbl.Creator:sub(2, -2),
							HeaderTbl.Description:sub(2, -2),
							tonumber(HeaderTbl.Entities),
							tonumber(HeaderTbl.Constraints),
							ExtraHeaderTbl.FileVersion,
							HeaderTbl.Date,
							ExtraHeaderTbl.Time, StartPos
						)
					elseif ( ExtraHeaderTbl.FileVersion <= 0.81 ) then
						tool:LoadFileCallBack( filepath,
							Data.Entities, Data.Constraints,
							{},{}, Data.HeadEntityIdx,
							Data.HoldAngle,
							Data.HoldPos, false,
							HeaderTbl.Creator:sub(2, -2),
							HeaderTbl.Description:sub(2, -2),
							tonumber(HeaderTbl.Entities),
							tonumber(HeaderTbl.Constraints),
							ExtraHeaderTbl.FileVersion,
							HeaderTbl.Date,
							ExtraHeaderTbl.Time
						)
					end

					elseif ( HeaderTbl.Type ) and ( HeaderTbl.Type == "Contraption Saver File" ) then
					Msg("AdvDupe:Loaded Contraption Saver file "..filepath.."  version: "..ExtraHeaderTbl.Version.."\n")
					pl:ConCommand( "adv_duplicator_height "..(Data.Height + 8) )
					tool:LoadFileCallBack( filepath,
						Data.Entities, Data.Constraints,
						{},{}, Data.Head, Angle(0,0,0), 
						Vector(0,0, -(Data.Height + 8)), --Data.Entities[Data.Head].LocalPos.z + 
						false, HeaderTbl.Creator:sub(2, -2),
						"Contraption Saver file v"..ExtraHeaderTbl.Version, 
						tonumber(HeaderTbl.Entities),
						tonumber(HeaderTbl.Constraints),
						tonumber(ExtraHeaderTbl.Version),
						HeaderTbl.Date,
						"n/a"
					)
				elseif (Data.Information) then
					Msg("AdvDupe:Loaded old Contraption Saver file version "..Data.Information.Version.."\n")
					local head,low
					for k,v in pairs(Data.Entities) do
						if (!head) or (v.Pos.z < low) then
							head = k
							low = v.Pos.z
						end
					end

					AdvDupe.ConvertPositionsToLocal( Data.Entities, Data.Constraints, Data.Entities[head].Pos + Vector(0,0,-15), Angle(0,0,0) )
					tool:LoadFileCallBack( filepath,
						Data.Entities, Data.Constraints,
						{},{}, head, Angle(0,0,0), Vector(0,0,0), false, 
						Data.Information.Creator,
						"Old Contraption Saver file v"..Data.Information.Version, 
						Data.Information.Entities,
						Data.Information.Constraints,
						Data.Information.Version,
						Data.Information.Date,
						"n/a"
					)
				else
					AdvDupe.SendClientError(pl, "Unknown File Type or Bad File")
					Msg("AdvDupeERROR: Unknown File Type or Bad File\n")
					AdvDupe.SetPercent( pl, -1 )
					return
				end
			end
			Serialiser.DeserialiseWithHeaders( temp, Load2NewFile, pl, filepath, tool )
			return --or it will try to load as an old file
		end

		temp = util.KeyValuesToTable(temp)
		if ( temp["VersionInfo"] or temp["versioninfo"] ) then --pre-0.6x file, it ueses a different meathod os stroing FullCase
			Msg("AdvDipe: Loading old legacy file type\n")
			temp = dupeshare.RebuildTableFromLoad_Old(temp)
		elseif ( temp["strtbl"] ) then -- v0.7x
			Msg("AdvDipe: Loading v0.7x file type\n")
			local StrTbl = temp["strtbl"]
			temp["strtbl"] = nil
			temp = dupeshare.RebuildTableFromLoad(temp, {}, StrTbl)
		else --0.6x
			Msg("AdvDipe: Loading v0.6x file type\n")
			temp = dupeshare.RebuildTableFromLoad(temp)
		end
		if (temp) and (temp["VersionInfo"]) and (temp["VersionInfo"]["FileVersion"] > AdvDupe.FileVersion) then
			Msg("AdvDupeINFO:File is newer than installed version, failure may occure, you should update.")
		end
		local function Load3(pl, filepath, tool, temp)
			if (temp) and (temp["VersionInfo"]) and (temp["VersionInfo"]["FileVersion"] >= 0.6) then
				Msg("AdvDupe:Loaded old file "..filepath.."  version: "..temp.VersionInfo.FileVersion.."\n")
				tool:LoadFileCallBack( filepath,
					temp.EntTables, temp.ConstraintTables, {},{}, 
					temp.HeadEntityIdx, temp.HoldAngle, temp.HoldPos, 
					false, temp.VersionInfo.Creator, temp.VersionInfo.Desc, 
					temp.VersionInfo.NumOfEnts, temp.VersionInfo.NumOfConst, 
					temp.VersionInfo.FileVersion
				)
			elseif (temp) and (temp["VersionInfo"]) and (temp["VersionInfo"]["FileVersion"] <= 0.4) then
				Msg("AdvDupe:Loaded old legacy file "..filepath.."  version: "..temp.VersionInfo.FileVersion.."\n")
				if (temp["VersionInfo"]["FileVersion"] <= 0.2) then
					temp.DupeInfo = {}
					for id, entTable in pairs(temp.Ents) do
						if (entTable.DupeInfo) then
							temp.DupeInfo[id] = entTable.DupeInfo
						end
					end
				end
				tool:LoadFileCallBack( filepath,
					temp.Ents, temp.Constraints, temp.DupeInfo, 
					(temp.DORInfo or {}), temp.HeadEntID, temp.HoldAngle, Vector(0,0,0), 
					true, temp.VersionInfo.Creator, temp.VersionInfo.Desc, 
					temp.VersionInfo.NumOfEnts, temp.VersionInfo.NumOfConst, 
					temp.VersionInfo.FileVersion
				)
			elseif (temp) and (temp["Information"]) then --Old Contrpation Saver File
				Msg("AdvDupe:Loading old Contraption Saver file.\n")
				local head,low
				for k,v in pairs(temp.Entities) do
					if (!head) or (v.Pos.z < low) then
						head = k
						low = v.Pos.z
					end
				end
				AdvDupe.ConvertPositionsToLocal( temp.Entities, temp.Constraints, temp.Entities[head].Pos, Angle(0,0,0) )
				tool:LoadFileCallBack( filepath,
					temp.Entities, temp.Constraints,
					{},{}, head, Angle(0,0,0), Vector(0,0,0),
					false, temp.Information.Creator, "Old Contrpaption Saver File",
					temp.Information.Entities, temp.Information.Constraints, 
					"Old Contrpaption Saver File", temp.Date
				)
			else
				Msg("AdvDupeERROR:FILE FAILED TO LOAD! something is wrong with this file:  "..filepath.."\n")
				AdvDupe.SendClientError( pl, "Failed loading file" )
				AdvDupe.SetPercent( pl, -1 )
			end
			AdvDupe.SetPercent(pl, 50)
		end
		AdvDupe.SetPercent(pl, 30)
		timer.Simple(.1, Load3, pl, filepath, tool, temp)
	end
	AdvDupe.SetPercent(pl, 10)
	timer.Simple(.1, Load1, pl, filepath, tool, file.Read(filepath))
end

function AdvDupe.GetSaveableEntity( Ent, Offset )
	if ( Ent.PreEntityCopy ) then Ent:PreEntityCopy() end
	local Tab = table.Copy( Ent:GetTable() )
	if ( Ent.PostEntityCopy ) then Ent:PostEntityCopy() end
	Tab.Angle = Ent:GetAngles()
	Tab.Pos = Ent:GetPos()
	Tab.PhysicsObjects =  Tab.PhysicsObjects or {}
	local iNumPhysObjects = Ent:GetPhysicsObjectCount()
	for Bone = 0, iNumPhysObjects-1 do 
		local PhysObj = Ent:GetPhysicsObjectNum( Bone )
		if ( PhysObj:IsValid() ) then
			Tab.PhysicsObjects[ Bone ] = Tab.PhysicsObjects[ Bone ] or {}
			Tab.PhysicsObjects[ Bone ].Pos = PhysObj:GetPos()
			Tab.PhysicsObjects[ Bone ].Angle = PhysObj:GetAngle()
			Tab.PhysicsObjects[ Bone ].Frozen = !PhysObj:IsMoveable()
		end
	end
	local FlexNum = Ent:GetFlexNum()
	for i = 0, FlexNum do
		Tab.Flex = Tab.Flex or {}
		Tab.Flex[ i ] = Ent:GetFlexWeight( i )
	end
	Tab.FlexScale = Ent:GetFlexScale()
	if ( Ent.OnEntityCopyTableFinish ) then Ent:OnEntityCopyTableFinish( Tab ) end
	Tab.Pos = Tab.Pos - Offset
	Tab.LocalPos = Tab.Pos * 1
	Tab.LocalAngle = Tab.Angle * 1
	if ( Tab.PhysicsObjects ) then
		for Num, Object in pairs(Tab.PhysicsObjects) do
			Object.Pos = Object.Pos - Offset
			Object.LocalPos = Object.Pos * 1
			Object.LocalAngle = Object.Angle * 1
			Object.Pos = nil
			Object.Angle = nil
		end
	end
	if ( Tab.CollisionGroup ) then
		if ( !Tab.EntityMods ) then Tab.EntityMods = {} end
		Tab.EntityMods.CollisionGroupMod = Tab.CollisionGroup
	end
	if (Ent:GetClass() == "gmod_cameraprop") then
		Tab.key = Ent:GetNetworkedInt(0)
	end
	local SaveableEntity = {}
	SaveableEntity.Class		 = Ent:GetClass()
	SaveableEntity.Model = table.concat( dupeshare.split( Ent:GetModel(), '\\+' ), "/" )
	SaveableEntity.LocalPos		 = Tab.LocalPos
	SaveableEntity.LocalAngle	 = Tab.LocalAngle
	SaveableEntity.BoneMods		 = table.Copy( Tab.BoneMods )
	SaveableEntity.EntityMods	 = table.Copy( Tab.EntityMods )
	SaveableEntity.PhysicsObjects = table.Copy( Tab.PhysicsObjects )
	if ( Ent:GetParent() ) and ( Ent:GetParent():IsValid() ) then
		SaveableEntity.SavedParentIdx = Ent:GetParent():EntIndex()
	end
	local EntityClass = duplicator.FindEntityClass( SaveableEntity.Class )
	if (!EntityClass) then return SaveableEntity end
	for iNumber, key in pairs( EntityClass.Args ) do
		if (key != "pos") and (key != "position") and (key != "Pos") and ( key != "model" ) and (key != "Model")
		and (key != "ang") and (key != "Ang") and (key != "angle") and (key != "Angle") and (key != "Class") then
			SaveableEntity[ key ] = Tab[ key ]
		end
	end
	return SaveableEntity	
end

function AdvDupe.GetSaveableConst( ConstraintEntity, Offset )
	if (!ConstraintEntity) then return {} end
	local SaveableConst = {}
	local ConstTable = ConstraintEntity:GetTable()
	local Factory = duplicator.ConstraintType[ ConstTable.Type ]
	if ( Factory ) then
		SaveableConst.Type = ConstTable.Type
		for k, Key in pairs( Factory.Args ) do
			if (!string.find(Key, "Ent") or string.len(Key) != 4)
			and (!string.find(Key, "Bone") or string.len(Key) != 5)
			and (Key != "Ent") and (Key != "Bone")
			and (ConstTable[ Key ]) and (ConstTable[ Key ] != false) then
				SaveableConst[ Key ] = ConstTable[ Key ]
			end
		end
	else
		table.Merge( SaveableConst, ConstraintEntity:GetTable() )
	end
	if ( ConstTable.Type == "Elastic" ) or ( ConstTable.length ) then
		SaveableConst.length = ConstTable.length
	end
	SaveableConst.Entity = {}
	local ents = {}
	if ( ConstTable[ "Ent" ] && ( ConstTable[ "Ent" ]:IsWorld() || ConstTable[ "Ent" ]:IsValid() ) ) then
		
		SaveableConst.Entity[ 1 ] = {}
		SaveableConst.Entity[ 1 ].Index	 	= ConstTable[ "Ent" ]:EntIndex()
		if ConstTable[ "Ent" ]:IsWorld() then SaveableConst.Entity[ 1 ].World = true end
		SaveableConst.Entity[ 1 ].Bone 		= ConstTable[ "Bone" ]
	else
		for i=1, 6 do
			if ( ConstTable[ "Ent"..i ] && ( ConstTable[ "Ent"..i ]:IsWorld() || ConstTable[ "Ent"..i ]:IsValid() ) ) then
				SaveableConst.Entity[ i ] = {}
				SaveableConst.Entity[ i ].Index	 	= ConstTable[ "Ent"..i ]:EntIndex()
				SaveableConst.Entity[ i ].Bone 		= ConstTable[ "Bone"..i ]
				SaveableConst.Entity[ i ].WPos 		= ConstTable[ "WPos"..i ]
				SaveableConst.Entity[ i ].Length 	= ConstTable[ "Length"..i ]
				if ConstTable[ "Ent"..i ]:IsWorld() then
					SaveableConst.Entity[ i ].World = true
					if ( ConstTable[ "LPos"..i ] ) then
						SaveableConst.Entity[ i ].LPos = ConstTable[ "LPos"..i ] - Offset
					else
						SaveableConst.Entity[ i ].LPos = Offset
					end
				else
					SaveableConst.Entity[ i ].LPos = ConstTable[ "LPos"..i ]
				end
				table.insert( ents, ConstTable[ "Ent"..i ] )
			end
		end
	end
	return SaveableConst, ents
end

function AdvDupe.Copy( Ent, EntTable, ConstraintTable, Offset )
	if ( !Ent or !Ent:IsValid() ) or ( EntTable[ Ent:EntIndex() ] ) 
	or ( ( Ent:GetClass() == "prop_physics" ) and ( Ent:GetVar("IsPlug",nil) == 1 ) ) then
		return EntTable, ConstraintTable
	end
	EntTable[ Ent:EntIndex() ] = AdvDupe.GetSaveableEntity( Ent, Offset )
	if ( !constraint.HasConstraints( Ent ) ) then return EntTable, ConstraintTable end
	for key, ConstraintEntity in pairs( Ent.Constraints ) do
		if ( !ConstraintTable[ ConstraintEntity ] ) then
			local ConstTable, ents = AdvDupe.GetSaveableConst( ConstraintEntity, Offset )
			ConstraintTable[ ConstraintEntity ] = ConstTable
			for k,e in pairs(ents) do
				if ( e and ( e:IsWorld() or e:IsValid() ) ) and ( !EntTable[ e:EntIndex() ] ) then
					AdvDupe.Copy( e, EntTable, ConstraintTable, Offset )
				end
			end
		end
	end
	return EntTable, ConstraintTable
end

function AdvDupe.GetEntitysConstrainedEntitiesAndConstraints( ent )
	if ( !Ent ) then return {},{} end
	local Consts, Ents = {},{}
	Ents[ Ent:EntIndex()] = Ent
	if ( constraint.HasConstraints( Ent ) ) then
		for key, ConstraintEntity in pairs( Ent.Constraints ) do
			local ConstTable = ConstraintEntity:GetTable()
			table.insert( Consts, ConstraintEntity )
			for i=1, 6 do
				if ( ConstTable[ "Ent"..i ] && ( ConstTable[ "Ent"..i ]:IsWorld() || ConstTable[ "Ent"..i ]:IsValid() ) ) then
					local ent = ConstTable[ "Ent"..i ]
					Ents[ ent:EntIndex() ] = ent
				end
			end
		end
	end
	return Ents, Consts
end

function AdvDupe.GetAllEnts( Ent, OrderedEntList, EntsTab, ConstsTab )
	if ( Ent and Ent:IsValid() ) and ( !EntsTab[ Ent:EntIndex() ] ) then
		EntsTab[ Ent:EntIndex() ] = Ent
		table.insert(OrderedEntList, Ent)
		if ( !constraint.HasConstraints( Ent ) ) then return OrderedEntList end
		for key, ConstraintEntity in pairs( Ent.Constraints ) do
			if ( !ConstsTab[ ConstraintEntity ] ) then
				ConstsTab[ ConstraintEntity ] = true
				local ConstTable = ConstraintEntity:GetTable()
				for i=1, 6 do
					local e = ConstTable[ "Ent"..i ]
					if ( e and e:IsValid() ) and ( !EntsTab[ e:EntIndex() ] ) then
						AdvDupe.GetAllEnts( e, OrderedEntList, EntsTab, ConstsTab )
					end
				end
			end
		end
	end
	return OrderedEntList
end

--
--	some modifer functions
--
local function CollisionGroupModifier(ply, Ent, group )
	
	if ( group == COLLISION_GROUP_WORLD ) then
		Ent:SetCollisionGroup( COLLISION_GROUP_WORLD )
		Ent.CollisionGroup = COLLISION_GROUP_WORLD
	else
		Ent:SetCollisionGroup( COLLISION_GROUP_NONE )
		Ent.CollisionGroup = COLLISION_GROUP_NONE
	end
	
end
duplicator.RegisterEntityModifier( "CollisionGroupMod", CollisionGroupModifier )

local function SetMassMod( Player, Entity, Data )

	if ( Data and Data.Mass ) then
		if (Data.Mass > 0) then
			Entity:GetPhysicsObject():SetMass(Data.Mass)
			duplicator.StoreEntityModifier( Entity, "MassMod", Data )
		else 
			self:GetOwner():ConCommand("weight_set 1");
			self:GetOwner():SendLua("ZMass()");
		end
		
		return true
	end
	
end
duplicator.RegisterEntityModifier( "MassMod", SetMassMod )

if (dupeshare and dupeshare.PublicDirs) then
	AdvDupe.PublicDirs = {}
	for k, v in pairs(dupeshare.PublicDirs) do
		local dir = dupeshare.BaseDir.."/"..v
		AdvDupe.PublicDirs[v] = dir
		if ( !file.Exists(dir) ) or ( file.Exists(dir) and !file.IsDir(dir) ) then 
			file.CreateDir( dir )
		end
	end
end

function AdvDupe.GetPlayersFolder(pl)
	local dir = dupeshare.BaseDir
	if (!SinglePlayer()) then
		local name = dupeshare.ReplaceBadChar(tostring(pl:SteamID()))
		if (name == "STEAM_ID_LAN") or (name == "UNKNOWN") or (name == "STEAM_ID_PENDING") then
			name = dupeshare.GetPlayerName(pl) or "unknown"
		end
		dir = dir.."/"..name
	end
	return dir
end

function AdvDupe.MakeDir(pl, cmd, args)
	if !pl:IsValid() or !pl:IsPlayer() or !args[1] then return end
	local dir = AdvDupe[pl].cdir
	local foldername = dupeshare.ReplaceBadChar(string.Implode(" ", args))
	AdvDupe.FileOpts(pl, "makedir", foldername, dir)
end
concommand.Add("adv_duplicator_makedir", AdvDupe.MakeDir)

local function FileOptsCommand(pl, cmd, args)
	if !pl:IsValid() or !pl:IsPlayer() or !args[1] then return end
	local action = args[1]
	--local filename = dupeshare.GetFileFromFilename(pl:GetInfo( "adv_duplicator_load_filename" ))..".txt"
	local filename = pl:GetInfo( "adv_duplicator_load_filename" )
	--local filename2 = pl:GetInfo( "adv_duplicator_load_filename2" )
	local dir	= AdvDupe[pl].cdir
	local dir2	= AdvDupe[pl].cdir2
	AdvDupe.FileOpts(pl, action, filename, dir, dir2)
end
concommand.Add("adv_duplicator_fileopts", FileOptsCommand)

local function FileOptsRenameCommand(pl, cmd, args)
	Msg("rename cmd\n")
	if !pl:IsValid() or !pl:IsPlayer() or !args[1] then return end
	--local filename = dupeshare.GetFileFromFilename(pl:GetInfo( "adv_duplicator_load_filename" ))..".txt"
	local filename = pl:GetInfo( "adv_duplicator_load_filename" )
	local dir	= AdvDupe[pl].cdir
	local newname = string.Implode(" ", args)
	newname = dupeshare.ReplaceBadChar(dupeshare.GetFileFromFilename(newname))..".txt"
	Msg("s-newname= "..newname.."\n")
	AdvDupe.FileOpts(pl, "rename", filename, dir, newname)
end
concommand.Add("adv_duplicator_fileoptsrename", FileOptsRenameCommand)

function AdvDupe.FileOpts(pl, action, filename, dir, dir2)
	if not filename or not dir then return end
	local file1 = dir.."/"..filename
	Msg("action= "..action.."  filename= "..filename.."  dir= "..dir.."  dir2= "..(dir2 or "none").."\n")
	if (!AdvDupe.CheckPerms(pl, "", dir, "access")) then return end
	if (action == "delete") and AdvDupe.CheckPerms(pl, "", dir, "delete") then
		file.Delete(file1)
		AdvDupe.HideGhost(pl, false)
		AdvDupe.UpdateList(pl)
	elseif (action == "copy") and AdvDupe.CheckPerms(pl, "", dir2, "write") then
		local file2 = dir2.."/"..filename
		if file.Exists(file2) then
			local filename2 = ""
			file2, filename2 = dupeshare.FileNoOverWriteCheck(dir2, filename)
			if dir == dir2 then
				AdvDupe.SendClientError(pl, "Destination Same as Source, Saved File as: "..filename2)
			else
				AdvDupe.SendClientError(pl, "File Exists at Destination, Saved File as: "..filename2)
			end
		end
		file.Write(file2, file.Read(file1))
		AdvDupe.UpdateList(pl)
	elseif (action == "move") and AdvDupe.CheckPerms(pl, "", dir, "delete") and AdvDupe.CheckPerms(pl, "", dir2, "write") then
		if dir == dir2 then
			AdvDupe.SendClientError(pl, "Cannot move file to same directory")
			return
		end
		AdvDupe.FileOpts(pl, "copy", filename, dir, dir2)
		AdvDupe.FileOpts(pl, "delete", filename, dir)
	elseif (action == "makedir") and AdvDupe.CheckPerms(pl, "", dir, "makedir") then
		if !SinglePlayer() and dupeshare.NamedLikeAPublicDir(filename) then
			AdvDupe.SendClientError(pl, "You Cannot Name a Folder Like a Public Folder")
			return
		end
		if file.Exists(file1) and file.IsDir(file1) then 
			AdvDupe.SendClientError(pl, "Folder Already Exists!")
			return
		end
		file.CreateDir(file1)
		AdvDupe.HideGhost(pl, false)
		AdvDupe.UpdateList(pl)
	elseif (action == "rename") and AdvDupe.CheckPerms(pl, "", dir, "delete") and AdvDupe.CheckPerms(pl, "", dir, "write") then
		AdvDupe.FileOpts(pl, "duplicate", filename, dir, dir2)
		AdvDupe.FileOpts(pl, "delete", filename, dir)
	elseif (action == "duplicate") and AdvDupe.CheckPerms(pl, "", dir, "write") then
		local file2 = dir.."/"..dir2 --using dir2 to hold the new filename
		if file.Exists(file2) then
			local filename2 = ""
			file2, filename2 = dupeshare.FileNoOverWriteCheck(dir, dir2)
			AdvDupe.SendClientError(pl, "File Exists With That Name Already, Renamed as: "..filename2)
		end
		file.Write(file2, file.Read(file1))
		AdvDupe.UpdateList(pl)
	else
		AdvDupe.SendClientError(pl, "FileOpts: Bad Action Command!")
	end
end

--TODO
function AdvDupe.CheckPerms(pl, dir, password, action)
	if (dupeshare.UsePWSys) and (!SinglePlayer()) then
		--todo
		return true
	else
		return true
	end
	AdvDupe.SendClientError(pl, "Permission error!")
end

--makes the player see an error
--todo: make enum error codes
function AdvDupe.SendClientError(pl, errormsg, NoSound)
	if ( !pl or !pl:IsValid() or !pl:IsPlayer() or !errormsg ) then return end
	--pl:SendLua( "dvdupeclient.Error( \""..errormsg.."\" )" )
	Msg("AdvDupe: Sending this ErrorMsg to "..tostring(pl).."\nAdvDupe-ERROR: \""..tostring(errormsg).."\"\n")
	umsg.Start("AdvDupeCLError", pl)
		umsg.String(errormsg)
		umsg.Bool(NoSound)
	umsg.End()
end
function AdvDupe.SendClientInfoMsg(pl, msg, NoSound)
	if ( !pl or !pl:IsValid() or !pl:IsPlayer() or !msg ) then return end
	--pl:SendLua( "dvdupeclient.Error( \""..errormsg.."\" )" )
	Msg("AdvDupe, Sending This InfoMsg to "..tostring(pl).."\nAdvDupe: \""..tostring(msg).."\"\n")
	umsg.Start("AdvDupeCLInfo", pl)
		umsg.String(msg)
		umsg.Bool(NoSound)
	umsg.End()
end

function AdvDupe.UpdateList(pl)
	if ( !pl or !pl:IsValid() or !pl:IsPlayer() ) then return end
	local tool = AdvDupe.GetAdvDupeToolObj(pl)
	if (tool) then
		tool:UpdateList()
	end
end

function AdvDupe.HideGhost(pl, Hide)
	if ( !pl or !pl:IsValid() or !pl:IsPlayer() ) then return end
	local tool = AdvDupe.GetAdvDupeToolObj(pl)
	if (tool) then
		tool:HideGhost(Hide)
	end
end

local function AdvDupe_HideGhost( pl, command, args )
	if ( !pl or !pl:IsValid() or !pl:IsPlayer() ) then return end
	if ( args[1] ) and  ( args[1] == "0" ) then
		AdvDupe.HideGhost(pl, false)
	elseif ( args[1] ) and  ( args[1] == "1" ) then
		AdvDupe.HideGhost(pl, true)
	end
end
concommand.Add( "adv_duplicator_hideghost", AdvDupe_HideGhost )

function AdvDupe.SetPasting(pl, Pasting)
	if ( !pl or !pl:IsValid() or !pl:IsPlayer() ) then return end
	AdvDupe[pl] = AdvDupe[pl] or {}
	AdvDupe[pl].Pasting = Pasting
	umsg.Start("AdvDupeSetPasting", pl)
		umsg.Bool(Pasting)
	umsg.End()
end

function AdvDupe.SetPercentText( pl, Txt )
	if ( !pl or !pl:IsValid() or !pl:IsPlayer() ) then return end
	AdvDupe[pl] = AdvDupe[pl] or {}
	AdvDupe[pl].PercentText = Txt
	umsg.Start("AdvDupe_Start_Percent", pl)
		umsg.String(Txt)
	umsg.End()
end

function AdvDupe.SetPercent(pl, Percent)
	if ( !pl or !pl:IsValid() or !pl:IsPlayer() ) then return end
	umsg.Start("AdvDupe_Update_Percent", pl)
		umsg.Short(Percent)
	umsg.End()
end

function AdvDupe.GetAdvDupeToolObj(pl)
	if pl:GetActiveWeapon() then
		if ( !pl or !pl:IsValid() or !pl:IsPlayer() ) then return end
		--local tool = pl:GetActiveWeapon()
		if ( !pl:GetActiveWeapon():GetTable().Tool ) then return end
		local tool = pl:GetActiveWeapon():GetTable().Tool.adv_duplicator.Weapon
		if ( dupeshare.CurrentToolIsDuplicator(tool) ) then
			return tool:GetTable():GetToolObject()
		end
		return 
	end
	return
end

AdvDupe.AdminSettings = {}
local MaxUploadLength = 5			--common to all players. it's the size of string in each piece, over 200 and it _WILL_ fail
local UploadPiecesPerSend = 2		--per player. number of pieces the player send per interval. over 5 and it _WILL_ fail
local UploadSendDelay = 0.25		--per player. seconds between each inverval. under 0.01 and there is no real difference. Don't set less than .1 and 2 above, it will fail a lot.
local MaxUploadSize = -1				--per player. -1 disable, 0 no limit, >0 K characers allowed (K = 1024).  allows up to (MaxUploadLength - 1) charcters over this limit.,
--download settings
local MaxDownloadLength = 200		--common to all players. same as above, but limit is _MAX_ 255, but it doesn't work well above 200
local DownloadPiecesPerSend = 3		--per player. same as above. set it to high and the server _WILL_ most likely crash
local DownloadSendInterval = 0.1	--per player. same as above. this should be the lowest you should use
local CanDownloadDefault = true		--per player. if you can't figure this out, i have a stick i can hit you with :V
--ghost settings	-- all per player
local LimitedGhost = false			--same as the panel option, just overrides that option with true
local GhostLimitNorm = 500			--max size of ghost. DON'T make it higher than this!!!!
local GhostLimitLimited = 50		--size of ghost when limiting is on. minimum is 1
local GhostAddDelay = .2			--delay between adding to the ghost when ghosting
local GhostsPerTick = 3				--ghost ents added per tick
--per player settings
local PlayerSettings = {}
--	uploads
local function SendUploadSettings( pl, pieces, delay )
	if ( pl ) and ( !pl:IsValid() or !pl:IsPlayer() ) then return end
	umsg.Start( "AdvDupeUploadSettings", pl )
		umsg.Short( MaxUploadLength )
		umsg.Short( pieces )
		umsg.Short( delay * 100 )
	umsg.End()
end

local function SendMaxUploadSize( pl, Kchars )
	if ( pl ) and ( !pl:IsValid() or !pl:IsPlayer() ) then return end
	umsg.Start( "AdvDupeMaxUploadSize", pl )
		umsg.Short( Kchars )
	umsg.End()
end

local function CanUpload( pl )
	if ( PlayerSettings[pl].MaxUploadSize >= 0 ) then return true end
end

local function GetMaxUpload( pl )
	return PlayerSettings[pl].MaxUploadSize * 1024
end

function AdvDupe.AdminSettings.DefaultUploadSettings( len, pieces, delay, Kchars, DontUpdatePl  )
	if ( type(len) != "number" ) or ( type(pieces) != "number" ) or ( type(delay) != "number" ) or ( type(Kchars) != "number" ) then return end
	len = math.floor(len)
	pieces = math.floor(pieces)
	Kchars = math.floor(Kchars)
	if ( len >= 50 ) then MaxUploadLength = len end
	if ( pieces >= 1 ) then UploadPiecesPerSend = pieces end
	if ( delay >= 0.01 ) and ( delay <= 2 ) then UploadSendDelay = delay end
	if ( Kchars >= -1 ) then MaxUploadSize = Kchars end
	if ( !DontUpdatePl ) then
		for pl,v in pairs( PlayerSettings ) do
			PlayerSettings[pl].UploadPiecesPerSend = UploadPiecesPerSend
			PlayerSettings[pl].UploadSendDelay = UploadSendDelay
			PlayerSettings[pl].MaxUploadSize = MaxUploadSize
		end
		SendUploadSettings( nil, pieces, delay )
		SendMaxUploadSize( nil, Kchars )
	end
end

function AdvDupe.AdminSettings.UploadSettings( pl, pieces, delay, Kchars )
	if ( !pl or !pl:IsValid() or !pl:IsPlayer() ) then return end
	if ( type(pieces) != "number" ) or ( type(delay) != "number" ) or ( type(Kchars) != "number" ) then return end
	pieces = math.floor(pieces)
	if ( pieces >= 1 ) then PlayerSettings[pl].UploadPiecesPerSend = pieces end
	if ( delay >= 0.01 ) and ( delay <= 2 ) then PlayerSettings[pl].UploadSendDelay = delay end
	SendUploadSettings( pl, pieces, delay )
	if ( Kchars ) then AdvDupe.AdminSettings.MaxUploadSize( pl, Kchars ) end
end

function AdvDupe.AdminSettings.MaxUploadSize( pl, Kchars )
	if ( !pl or !pl:IsValid() or !pl:IsPlayer() ) then return end
	if ( type(Kchars) != "number" ) then return end
	Kchars = math.floor(Kchars)
	if ( Kchars >= -1 ) then
		PlayerSettings[pl].MaxUploadSize = Kchars
		SendMaxUploadSize( pl, Kchars )
	end
end
function AdvDupe.AdminSettings.HaltUpload( pl )
	umsg.Start( "AdvDupeHaltUpload", pl )
	umsg.End()
end
function AdvDupe.RecieveFileContentStart( pl, cmd, args )
	 AdvDupe.AdminSettings.UploadSettings( pl, tonumber(args[1]), tonumber(args[2]), tonumber(args[3] or 0) )
end
concommand.Add("AdvDupe_UploadSettings", AdvDupe.RecieveFileContentStart)
--
--	downlaods
local function SendCanDownload( pl, candownload )
	if ( pl ) and ( !pl:IsValid() or !pl:IsPlayer() ) then return end
	umsg.Start( "AdvDupeCanDownload", pl )
		umsg.Bool( candownload )
	umsg.End()
end
function AdvDupe.AdminSettings.DefaultDownloadSettings( len, pieces, delay, candownload, DontUpdatePl )
	if ( type(len) != "number" ) or ( type(pieces) != "number" ) or ( type(delay) != "number" ) then return end
	len = math.floor(len)
	pieces = math.floor(pieces)
	if ( len >= 50 ) then MaxDownloadLength = len end
	if ( pieces >= 1 ) then DownloadPiecesPerSend = pieces end
	if ( delay >= 0.01 ) and ( delay <= 2 ) then DownloadSendInterval = delay end
	if ( candownload ) then CanDownload = true else CanDownload = false end
	if ( !DontUpdatePl ) then
		for pl,v in pairs( PlayerSettings ) do
			PlayerSettings[pl].DownloadPiecesPerSend = DownloadPiecesPerSend
			PlayerSettings[pl].DownloadSendInterval = DownloadSendInterval
			PlayerSettings[pl].CanDownload = CanDownload
		end
	end
end
function AdvDupe.AdminSettings.DownloadSettings( pl, pieces, delay, candownload )
	if ( !pl or !pl:IsValid() or !pl:IsPlayer() ) then return end
	if ( type(pieces) != "number" ) or ( type(delay) != "number" ) then return end
	pieces = math.floor(pieces)
	if ( pieces >= 1 ) then PlayerSettings[pl].DownloadPiecesPerSend = pieces end
	if ( delay >= 0.01 ) and ( delay <= 2 ) then PlayerSettings[pl].DownloadSendInterval = delay end
	if ( candownload != nil ) then AdvDupe.AdminSettings.SetCanDownload( pl, candownload ) end
end
function AdvDupe.AdminSettings.SetCanDownload( pl, candownload )
	if ( !pl or !pl:IsValid() or !pl:IsPlayer() ) then return end
	if ( candownload ) then PlayerSettings[pl].CanDownload = true
	else PlayerSettings[pl].CanDownload = false end
	SendCanDownload( pl, PlayerSettings[pl].CanDownload )
end
local function CanDownload( pl )
	return PlayerSettings[pl].CanDownload
end
function AdvDupe.LimitedGhost( pl )
	return PlayerSettings[pl].LimitedGhost
end
function AdvDupe.GhostLimitNorm( pl )
	return PlayerSettings[pl].GhostLimitNorm
end
function AdvDupe.GhostLimitLimited( pl )
	return PlayerSettings[pl].GhostLimitLimited
end
function AdvDupe.GhostAddDelay( pl )
	return PlayerSettings[pl].GhostAddDelay
end
function AdvDupe.GhostsPerTick( pl )
	return PlayerSettings[pl].GhostsPerTick
end
function AdvDupe.AdminSettings.DefaultGhostSettings( normsize, limitsize, delay, num, limited, DontUpdatePl )
	if ( type(normsize) != "number" ) or ( type(limitsize) != "number" ) or ( type(delay) != "number" ) or ( type(num) != "number" ) then return end
	normsize = math.floor(normsize)
	limitsize = math.floor(limitsize)
	num = math.floor(num)
	if ( normsize > 0 ) then GhostLimitNorm = normsize end
	if ( limitsize > 0 ) then GhostLimitLimited = limitsize end
	if ( delay >= 0.01 ) and ( delay <= 1 ) then GhostAddDelay = delay end
	if ( num > 0 ) then GhostsPerTick = num end
	if ( limited ) then LimitedGhost = true else LimitedGhost = false end
	if ( !DontUpdatePl ) then
		for pl,v in pairs( PlayerSettings ) do
			PlayerSettings[pl].GhostLimitNorm = GhostLimitNorm
			PlayerSettings[pl].GhostLimitLimited = GhostLimitLimited
			PlayerSettings[pl].GhostAddDelay = GhostAddDelay
			PlayerSettings[pl].LimitedGhost = LimitedGhost
			PlayerSettings[pl].GhostsPerTick = GhostsPerTick
		end
	end
end
function AdvDupe.AdminSettings.GhostSettings( pl, normsize, limitsize, delay, num, limited )
	if ( !pl or !pl:IsValid() or !pl:IsPlayer() ) then return end
	if ( type(normsize) != "number" ) or ( type(limitsize) != "number" ) or ( type(delay) != "number" ) or ( type(num) != "number" ) then return end
	normsize = math.floor(normsize)
	limitsize = math.floor(limitsize)
	num = math.floor(num)
	if ( normsize > 0 ) then PlayerSettings[pl].GhostLimitNorm = normsize end
	if ( limitsize > 0 ) then PlayerSettings[pl].GhostLimitLimited = limitsize end
	if ( delay >= 0.01 ) and ( delay <= 1 ) then PlayerSettings[pl].GhostAddDelay = delay end
	if ( num > 0 ) then PlayerSettings[pl].GhostsPerTick = num end
	if ( limited != nil ) then AdvDupe.AdminSettings.SetLimitedGhost( pl, limited ) end
end
function AdvDupe.AdminSettings.SetLimitedGhost( pl, limited )
	if ( !pl or !pl:IsValid() or !pl:IsPlayer() ) then return end
	if ( limited ) then PlayerSettings[pl].LimitedGhost = true
	else PlayerSettings[pl].LimitedGhost = false end
end

function AdvDupe.AdminSettings.SetPlayerToDefault( pl )
	if ( !pl or !pl:IsValid() or !pl:IsPlayer() ) then return end
	PlayerSettings[pl] = {}
	AdvDupe.AdminSettings.UploadSettings( pl, UploadPiecesPerSend, UploadSendDelay, MaxUploadSize )
	AdvDupe.AdminSettings.DownloadSettings( pl, DownloadPiecesPerSend, DownloadSendInterval, CanDownload )
	AdvDupe.AdminSettings.GhostSettings( pl, GhostLimitNorm, GhostLimitLimited, GhostAddDelay, GhostsPerTick, LimitedGhost )
end
hook.Add( "PlayerInitialSpawn", "AdvDupePlayerJoinSettings", AdvDupe.AdminSettings.SetPlayerToDefault )
--
--	Upload: Recieves file from client
--
function AdvDupe.RecieveFileContentStart( pl, cmd, args )
	if ( !pl:IsValid() or !pl:IsPlayer() ) then return end
	Msg("AdvDupe: Ready to recieve file: \""..args[2].."\" from player: "..(pl:GetName() or "unknown").."\n")
	if ( !CanUpload( pl ) ) then
		Msg("player \""..tostring(pl).."\" not allowed to upload\n")
		return
	end
	if (!AdvDupe[pl]) then AdvDupe[pl] = {} end
	AdvDupe[pl].templast		= tonumber(args[1])
	AdvDupe[pl].tempfile		= nil
	if ( GetMaxUpload(pl) > 0 and (AdvDupe[pl].templast - 1) * MaxUploadLength > GetMaxUpload(pl) ) then
		Msg("player \""..tostring(pl).."\" is trying to upload over "..(AdvDupe[pl].templast - 1) * MaxUploadLength.." then limit is "..MaxUploadSize.."\n")
		SendMaxUploadSize( pl ) --tell the player what the max is
		AdvDupe.AdminSettings.HaltUpload( pl )
		return
	end
	AdvDupe[pl].tempdir			= AdvDupe[pl].cdir --upload into curent open dir
	AdvDupe[pl].tempfilename	= args[2]
	AdvDupe[pl].tempnum			= 0
	AdvDupe[pl].tempfile		= {}
end
concommand.Add("DupeRecieveFileContentStart", AdvDupe.RecieveFileContentStart)

function AdvDupe.RecieveFileContent( pl, cmd, args )
	if ( !pl:IsValid() or !pl:IsPlayer() ) or ( !AdvDupe[pl].tempfile ) or (!args[1]) or (args[1] == "") then return end
	--Msg("AdvDupe: Recieving piece ")
	AdvDupe[pl].tempnum = AdvDupe[pl].tempnum + 1
	--Msg(args[1].." / "..AdvDupe[pl].templast.." received: "..AdvDupe[pl].tempnum.."\n")
	AdvDupe[pl].tempfile[tonumber(args[1])] = args[2]
	if (AdvDupe[pl].templast == AdvDupe[pl].tempnum) then
		AdvDupe.RecieveFileContentFinish( pl )
	end
	
end
concommand.Add("_DFC", AdvDupe.RecieveFileContent)

function AdvDupe.RecieveFileContentFinish( pl, cmd, args )
	if (!pl:IsValid() or !pl:IsPlayer()) or (!AdvDupe[pl].tempfile) then return end
	--local filepath = dupeshare.FileNoOverWriteCheck( AdvDupe.GetPlayersFolder(pl), AdvDupe[pl].tempfilename )
	local filepath = dupeshare.FileNoOverWriteCheck( AdvDupe[pl].tempdir, AdvDupe[pl].tempfilename )
	Msg("AdvDupe: Saving "..(pl:GetName() or "unknown").."'s recieved file to "..filepath.."\n")
	timer.Simple( .5, AdvDupe.RecieveFileContentSave, pl, filepath )
end
concommand.Add("DupeRecieveFileContentFinish", AdvDupe.RecieveFileContentFinish)

function AdvDupe.RecieveFileContentSave( pl, filepath )
	if (!pl:IsValid() or !pl:IsPlayer()) or (!AdvDupe[pl].tempfile) then return end
	local expected = AdvDupe[pl].templast
	local got = AdvDupe[pl].tempnum
	local FileName = dupeshare.GetFileFromFilename( filepath )
	if (expected != got) then
		--reassemble the pieces
		local txt = "AdvDupe: Missing piece(s): " 
		for i = 1,expected do
			if (!AdvDupe[pl].tempfile[i]) then
				txt = txt .. i .. ", "
			end
		end
		Msg(txt.."\n")
		AdvDupe.SendClientError(pl, "ERROR: \""..FileName.."\", failed uploading", true)
		AdvDupe.SendClientError(pl, "Server expected "..expected.." pieces but got "..got)
		AdvDupe.SendClientInfoMsg(pl, "Try resending it.", true)
		pl:PrintMessage(HUD_PRINTCONSOLE, "AdvDupeERROR: Your file, \""..FileName.."\", was not recieved properly\nAdvDupe: server expected "..expected.." pieces but got "..got)
		Msg("AdvDupe: This file, \""..filepath.."\", was not recieved properly\nAdvDupe: expected: "..expected.." pieces but got: "..got.."\n")
		umsg.Start("AdvDupeClientSendFinishedFailed", pl)
		umsg.End()
		return
	end
	--reassemble the pieces
	local temp = table.concat(AdvDupe[pl].tempfile)
	temp = dupeshare.DeCompress(temp, true)
	file.Write(filepath, temp)
	AdvDupe[pl].tempfile	 = nil
	--pl:PrintMessage(HUD_PRINTTALK, "Your file: \""..filepath.."\" was uploaded to the server")
	AdvDupe.SendClientInfoMsg(pl, "Your file: \""..FileName.."\" was uploaded to the server")
	pl:PrintMessage(HUD_PRINTCONSOLE, "Your file: \""..FileName.."\" was uploaded to the server")
	Msg("player: \""..(pl:GetName() or "unknown").."\" uploaded file: \""..filepath.."\"\n")
	AdvDupe.UpdateList(pl)
	umsg.Start("AdvDupeClientSendFinished", pl)
	umsg.End()
end
--
--	Download: Sends a file to the client
--
AdvDupe.SendBuffer = {}
function AdvDupe.SendSaveToClient( pl, filename )
	if ( !CanDownload( pl ) ) then return end
	if (!AdvDupe[pl]) then AdvDupe[pl] = {} end
	if (AdvDupe.SendBuffer[pl]) then return end --then were sending already and give up
	local filepath = filename
	local dir = "adv_duplicator"
	local ndir = dir.."/"..dupeshare.GetPlayerName(pl)
	if !file.Exists(filepath) then --if filepath was just a file name then try to find the file, for sending from list
		if !file.Exists(dir.."/"..filename) && !file.Exists(ndir.."/"..filename) then
			Msg("AdvDupe: File not found: \""..filepath.."\"\n")
			return
		end
		if ( file.Exists(ndir.."/"..filename) ) then filepath = ndir.."/"..filename end
		if ( file.Exists(dir.."/"..filename) ) then filepath = dir.."/"..filename end
	end
	filename = dupeshare.GetFileFromFilename(filepath)
	AdvDupe.SendBuffer[pl] = file.Read(filepath)
	AdvDupe.SendBuffer[pl] = dupeshare.Compress(AdvDupe.SendBuffer[pl], false)
	local len = string.len(AdvDupe.SendBuffer[pl])
	local last = math.ceil(len / MaxDownloadLength)
	umsg.Start("AdvDupeRecieveSaveStart", pl)
		umsg.Short(last)
		umsg.String(filename)
	umsg.End()
	Msg("AdvDupe: sending file \""..filename..".txt\" in "..tostring(last).." pieces. len: "..tostring(len).."\n")
	Msg("send rate: "..PlayerSettings[pl].DownloadSendInterval.."\n")
	timer.Simple( PlayerSettings[pl].DownloadSendInterval, AdvDupe.SendSaveToClientData, pl, 1, last )
end

function AdvDupe.SendSaveToClientData(pl, offset, last)
	if (!pl or !pl:IsValid() or !pl:IsPlayer()) or (!AdvDupe.SendBuffer[pl]) then return end
	for k=1, PlayerSettings[pl].DownloadPiecesPerSend do --sends three pieces
		--Msg("AdvDupe: sending piece: "..offset.." / "..last.."\n")
		local SubStrStart = (offset - 1) * MaxDownloadLength
		local str = ""
		if ( offset == last ) then
			--umsg.String(string.Right(AdvDupe[pl].temp, (len - ((last - 2) * MaxDownloadLength))))
			str = AdvDupe.SendBuffer[pl]:sub( SubStrStart )
			Msg("AdvDupe: send last piece\n")
		else
			--umsg.String(string.Right(string.Left(AdvDupe[pl].temp, ((offset + k) * MaxDownloadLength)),MaxDownloadLength))
			str = AdvDupe.SendBuffer[pl]:sub( SubStrStart, SubStrStart + MaxDownloadLength - 1 )
		end
		umsg.Start("AdvDupeRecieveSaveData", pl)
			umsg.Short(offset) --cause sometimes these are reccieved out of order
			umsg.String( str )
		umsg.End()
		offset = offset + 1
		if ( offset > last ) then break end
	end
	if ( offset <= last ) then
		timer.Simple( PlayerSettings[pl].DownloadSendInterval, AdvDupe.SendSaveToClientData, pl, offset, last )
	else
		AdvDupe.SendBuffer[pl] = nil --clear this to send again
		--inform the client they finished downloading in case they didn't notice
		umsg.Start("AdvDupeClientDownloadFinished", pl)
		umsg.End()
	end
end
local UseTimedPasteThreshold = 0
local PasteEntsPerTick = 1
local PostEntityPastePerTick = 2
local PasteConstsPerTick = 5
local DelayAfterPaste = 2
local PasterInstantPasteThreshold = 0
local PasterClearToPasteDelay = 3
function AdvDupe.GetPasterClearToPasteDelay()
	return PasterClearToPasteDelay
end
local DoPasteFX = false
local UseTaskSwitchingPaste = false
local DebugWeldsByDrawingThem = false
local DontAllowPlayersAdminOnlyEnts = true
local function SetTimedPasteVars(pl, cmd, args)
	if ( args[1] ) and ( ( pl:IsAdmin() ) or ( pl:IsSuperAdmin( )() ) ) then
		if args[1] then 
			UseTimedPasteThreshold = tonumber( args[1] )
		end
		if args[2] then
			PasteEntsPerTick = tonumber( args[2] )
		end
		if args[3] then
			PostEntityPastePerTick = tonumber( args[3] )
		end
		if args[4] then
			PasteConstsPerTick = tonumber( args[4] )
		end
		pl:PrintMessage(HUD_PRINTCONSOLE, "\nAdvDupe_SetTimedPasteVars:\n\tUseTimedPasteThreshold = "..UseTimedPasteThreshold.."\n\tPasteEntsPerTick = "..PasteEntsPerTick.."\n\tPostEntityPastePerTick = "..PostEntityPastePerTick.."\n\tPasteConstsPerTick = "..PasteConstsPerTick.."\nDefault: 100, 2, 20, 10\n")
	else
		pl:PrintMessage(HUD_PRINTCONSOLE, "Usage: \n  AdvDupe_SetTimedPasteVars <UseTimedPasteThreshold> [PasteEntsPerTick] [PostEntityPastePerTick] [PasteConstsPerTick]\nDefault: 100, 2, 20, 10\n")
	end
end
concommand.Add( "AdvDupe_SetTimedPasteVars", SetTimedPasteVars )

function AdvDupe.AdminSettings.SetTimedPasteVars( a, b, c, d )
	if ( a and type( a ) == "number" ) then UseTimedPasteThreshold = a end
	if ( b and type( b ) == "number" ) then PasteEntsPerTick = b end
	if ( c and type( c ) == "number" ) then PostEntityPastePerTick = c end
	if ( d and type( d ) == "number" ) then PasteConstsPerTick = d end
end
function AdvDupe.AdminSettings.SetUseTimedPasteThreshold( a ) if ( a and type( a ) == "number" ) then UseTimedPasteThreshold = a end end
function AdvDupe.AdminSettings.SetPasteEntsPerTick( a ) if ( a and type( a ) == "number" ) then PasteEntsPerTick = a end end
function AdvDupe.AdminSettings.SetPostEntityPastePerTick( a ) if ( a and type( a ) == "number" ) then PostEntityPastePerTick = a end end
function AdvDupe.AdminSettings.SetPasteConstsPerTick( a ) if ( a and type( a ) == "number" ) then PasteConstsPerTick = a end end

local function SetPasterInstantPasteThreshold(pl, cmd, args)
	if ( args[1] ) and ( ( pl:IsAdmin() ) or ( pl:IsSuperAdmin( )() ) ) then
		PasterInstantPasteThreshold = tonumber( args[1] )
	end
	pl:PrintMessage(HUD_PRINTCONSOLE, "\nPasterInstantPasteThreshold = "..PasterInstantPasteThreshold.." (Default: 50)\n")
end
concommand.Add( "AdvDupe_SetPasterInstantPasteThreshold", SetPasterInstantPasteThreshold )
function AdvDupe.AdminSettings.SetPasterInstantPasteThreshold( a ) if ( a and type( a ) == "number" ) then PasterInstantPasteThreshold = a end end

local function SetPasterClearToPasteDelay(pl, cmd, args)
	if ( args[1] ) and ( ( pl:IsAdmin() ) or ( pl:IsSuperAdmin( )() ) ) then
		PasterClearToPasteDelay = tonumber( args[1] )
	end
	pl:PrintMessage(HUD_PRINTCONSOLE, "\nPasterClearToPasteDelay = "..PasterClearToPasteDelay.." (Default: 3)\n")
end
concommand.Add( "AdvDupe_SetPasterClearToPasteDelay", SetPasterClearToPasteDelay )
function AdvDupe.AdminSettings.SetPasterClearToPasteDelay( a ) if ( a and type( a ) == "number" ) then PasterClearToPasteDelay = a end end

local function SetUseTaskSwitchingPaste(pl, cmd, args)
	if ( !pl:IsAdmin() and !pl:IsSuperAdmin( )() ) then return end
	if ( args[1] ) then
		if args[1] == "1" or args[1] == 1 then 
			UseTaskSwitchingPaste = true
		elseif args[1] == "0" or args[1] == 0 then
			UseTaskSwitchingPaste = false
		else
			pl:PrintMessage(HUD_PRINTCONSOLE, "Only takes 0 or 1")
		end
	end
	pl:PrintMessage(HUD_PRINTCONSOLE, "\n  AdvDupe_UseTaskSwitchingPaste = "..tostring(UseTaskSwitchingPaste).."  ( norm: False(0) )\n")
end
concommand.Add( "AdvDupe_UseTaskSwitchingPaste", SetUseTaskSwitchingPaste )

function AdvDupe.AdminSettings.SetUseTaskSwitchingPaste( a )
	if ( a ) then
		UseTaskSwitchingPaste = true
	else
		UseTaskSwitchingPaste = false
	end
end

local function SetDoPasteFX(pl, cmd, args)
	if ( !pl:IsAdmin() and !pl:IsSuperAdmin( )() ) then return end
	if ( args[1] ) then
		if args[1] == "1" or args[1] == 1 then 
			DoPasteFX = true
		elseif args[1] == "0" or args[1] == 0 then
			DoPasteFX = false
		else
			pl:PrintMessage(HUD_PRINTCONSOLE, "Only takes 0 or 1")
		end
	end
	pl:PrintMessage(HUD_PRINTCONSOLE, "\n  AdvDupe_DoPasteFX = "..tostring(DoPasteFX).."  ( norm: False(0) )\n")
end
concommand.Add( "AdvDupe_DoPasteFX", SetDoPasteFX )

function AdvDupe.AdminSettings.SetDoPasteFX( a )
	if ( a ) then
		DoPasteFX = true
	else
		DoPasteFX = false
	end
end

local function SetDebugWeldsByDrawingThem(pl, cmd, args)
	if ( !pl:IsAdmin() and !pl:IsSuperAdmin( )() ) then return end
	if ( args[1] ) then
		if args[1] == "1" or args[1] == 1 then 
			DebugWeldsByDrawingThem = true
		elseif args[1] == "0" or args[1] == 0 then
			DebugWeldsByDrawingThem = false
		else
			pl:PrintMessage(HUD_PRINTCONSOLE, "Only takes 0 or 1")
		end
	end
	pl:PrintMessage(HUD_PRINTCONSOLE, "\n  AdvDupe_DebugWeldsByDrawingThem = "..tostring(DebugWeldsByDrawingThem).."  ( norm: False(0) )\n")
end
concommand.Add( "AdvDupe_DebugWeldsByDrawingThem", SetDebugWeldsByDrawingThem )

function AdvDupe.AdminSettings.SetDebugWeldsByDrawingThem( a )
	if ( a ) then
		DebugWeldsByDrawingThem = true
	else
		DebugWeldsByDrawingThem = false
	end
end

local function SetDontAllowPlayersAdminOnlyEnts(pl, cmd, args)
	if ( !pl:IsAdmin() and !pl:IsSuperAdmin( )() ) then return end
	if ( args[1] ) then
		if args[1] == "1" or args[1] == 1 then 
			DontAllowPlayersAdminOnlyEnts = true
		elseif args[1] == "0" or args[1] == 0 then
			DontAllowPlayersAdminOnlyEnts = false
		else
			pl:PrintMessage(HUD_PRINTCONSOLE, "Only takes 0 or 1")
		end
	end
	pl:PrintMessage(HUD_PRINTCONSOLE, "\n  AdvDupe_DontAllowPlayersAdminOnlyEnts = "..tostring(DontAllowPlayersAdminOnlyEnts).."  ( norm: True(1) )\n")
end
concommand.Add( "AdvDupe_DontAllowPlayersAdminOnlyEnts", SetDontAllowPlayersAdminOnlyEnts )

function AdvDupe.AdminSettings.SetDontAllowPlayersAdminOnlyEnts( a )
	if ( a ) then
		DontAllowPlayersAdminOnlyEnts = true
	else
		DontAllowPlayersAdminOnlyEnts = false
	end
end

local DisallowedClasses = {}
function AdvDupe.AdminSettings.ChangeDisallowedClass( ClassName, DisallowPlayers, DisallowAdminsToo )
	if (!DisallowPlayers) then DisallowedClasses[ClassName] = nil
	elseif (DisallowAdminsToo) then DisallowedClasses[ClassName] = 2
	else DisallowedClasses[ClassName] = 1 end
end
local TimedPasteDataNum = 0
local TimedPasteDataCurrent = 1
local TimedPasteData = {}
local NextPasteTime = 0
local LastDelay = 0
local Timers = {}
local function AdvDupeThink()
	if (CurTime() >= NextPasteTime) then
		NextPasteTime = CurTime() +  .30
		if TimedPasteData[TimedPasteDataCurrent] then
			if ( !TimedPasteData[TimedPasteDataCurrent].Shooting_Ent )
			or ( !TimedPasteData[TimedPasteDataCurrent].Shooting_Ent.Entity )
			or ( !TimedPasteData[TimedPasteDataCurrent].Shooting_Ent.Entity:IsValid() ) then
				for k, ent in pairs( TimedPasteData[TimedPasteDataCurrent].CreatedEntities ) do
					if ( ent != nil and ent != NULL and ent.IsValid and ent:IsValid() ) then
						ent:Remove()
					end
				end
				AdvDupe.FinishPasting( TimedPasteData,TimedPasteDataCurrent )
				NextPasteTime = CurTime() +  .30
			else
				if ( TimedPasteData[TimedPasteDataCurrent].NormPaste ) and ( TimedPasteData[TimedPasteDataCurrent].Delay < CurTime() ) then
					local NoFail, Result = pcall( AdvDupe.NormPasteFromTable, TimedPasteData[TimedPasteDataCurrent] )
					if ( !NoFail ) then
					end
					AdvDupe.FinishPasting( TimedPasteData, TimedPasteDataCurrent )
					NextPasteTime = CurTime() + DelayAfterPaste
				elseif ( TimedPasteData[TimedPasteDataCurrent].Delay < CurTime() ) then
					local NoFail, Result = pcall( AdvDupe.OverTimePasteProcessFromTable )
					if ( !NoFail ) then
						TimedPasteData[TimedPasteDataCurrent].Stage = 5
					end
					TimedPasteData[TimedPasteDataCurrent].CallsInRun = TimedPasteData[TimedPasteDataCurrent].CallsInRun + 1
					if ( TimedPasteData[TimedPasteDataCurrent].Stage ) and ( TimedPasteData[TimedPasteDataCurrent].Stage == 5 ) then
						AdvDupe.FinishPasting( TimedPasteData,TimedPasteDataCurrent )
						NextPasteTime = CurTime() +  DelayAfterPaste
					else
						if ( !TimedPasteData[TimedPasteDataCurrent].DontRemoveThinger ) then
							AdvDupe.SetPercent(TimedPasteData[TimedPasteDataCurrent].Player, 
								(TimedPasteData[TimedPasteDataCurrent].CallsInRun / TimedPasteData[TimedPasteDataCurrent].TotalTicks) * 100)
						end
						LastDelay = .30 + .10* TimedPasteData[TimedPasteDataCurrent].CallsInRun / 20
						
						NextPasteTime = CurTime() + LastDelay
						
					end
					
				end
				if ( UseTaskSwitchingPaste) and ( TimedPasteData[TimedPasteDataCurrent + 1] ) then
					TimedPasteDataCurrent = TimedPasteDataCurrent + 1
				elseif ( UseTaskSwitchingPaste ) then
					TimedPasteDataCurrent = 1
				end
			end
		elseif TimedPasteDataCurrent != 1 then
			TimedPasteDataCurrent = 1
		end
	end

	for key, value in pairs( Timers ) do
		if ( value.Finish <= CurTime() ) then
			local b, e = pcall( value.Func, unpack( value.FuncArgs ) )
			if ( !b ) then
				if ( value.OnFailFunc ) then
					local b, e = pcall( value.OnFailFunc, unpack( value.OnFailArgs ) )
					if ( !b ) then
					end
				end
			end
			Timers[ key ] = nil
		end
	end
	
end
hook.Add("Think", "AdvDupe_Think", AdvDupeThink)

local function ReAddAdvDupeThink( ply, cmd, args )
	hook.Add("Think", "AdvDupe_Think", AdvDupeThink)
	pl:PrintMessage(HUD_PRINTCONSOLE, "ReAdded AdvDupe_Think Hook\n")
end 
concommand.Add( "AdvDupe_ReAdd_Think", ReAddAdvDupeThink ) 

local function RestartAdvDupeThink( ply, cmd, args )
	if ( !pl:IsAdmin() and !pl:IsSuperAdmin( )() ) then return end
	hook.Remove("Think", "AdvDupe_Think", AdvDupeThink)
	TimedPasteDataNum = 0
	TimedPasteDataCurrent = 1
	NextPasteTime = 0
	for n,d in pairs(TimedPasteData) do
		if ( d.Shooting_Ent ) and ( d.Shooting_Ent.Entity ) and ( d.Shooting_Ent.Entity:IsValid() ) then
			d.Shooting_Ent.Entity:Remove()
		end
		d = nil
	end
	TimedPasteData = {}
	hook.Add("Think", "AdvDupe_Think", AdvDupeThink)
	pl:PrintMessage(HUD_PRINTCONSOLE, "Restarted AdvDupe_Think Hook\n")
end 
concommand.Add( "AdvDupe_Restart_Think", RestartAdvDupeThink ) 



function AdvDupe.MakeTimer( Delay, Func, FuncArgs, OnFailFunc, OnFailArgs )
	if ( !Delay or !Func ) then Msg("AdvDupe.MakeTimer: Missings arg\n"); return end
	FuncArgs = FuncArgs or {}
	OnFailArgs = OnFailArgs or {}
	local timer			= {}
	timer.Finish		= CurTime() + Delay --UnPredictedCurTime()
	timer.Func			= Func
	timer.FuncArgs		= FuncArgs
	timer.OnFailFunc	= OnFailFunc
	timer.OnFailArgs	= OnFailArgs
	table.insert( Timers, timer )
	hook.Add("Think", "AdvDupe_Think", AdvDupeThink)
	return true;
end

local function MakeThinger(Player, Hide)
	local Shooting_Ent = ents.Create( "base_gmodentity" )
		Shooting_Ent:SetModel( "models/props_lab/labpart.mdl" )
		Shooting_Ent:SetAngles( Player:GetAimVector():Angle() )
		Shooting_Ent:SetPos( Player:GetShootPos() + (Player:GetAimVector( ) * 24) - Vector(0,0,20) )
		Shooting_Ent:SetNotSolid(true)
	Shooting_Ent:Spawn()
	if ( Shooting_Ent:GetPhysicsObject():IsValid() ) then
		Shooting_Ent:GetPhysicsObject():EnableMotion(false)
	end
	Shooting_Ent:Activate()
	
	Shooting_Ent:SetNoDraw(Hide)
	Shooting_Ent:SetOverlayText("AdvDupe Paster")
	Player:AddCleanup( "duplicates", Shooting_Ent )
	undo.Create( "Duplicator" )
		undo.AddEntity( Shooting_Ent )
		undo.SetPlayer( Player )
	undo.Finish()
	return Shooting_Ent
end

local function TingerFX( Shooting_Ent, HitPos )
	if (!Shooting_Ent) or (!Shooting_Ent.Entity) or (!Shooting_Ent.Entity:IsValid()) then return end
	local effectdata = EffectData()
		effectdata:SetOrigin( HitPos )
		effectdata:SetStart( Shooting_Ent.Entity:GetPos() )
	util.Effect( "PasteBeam", effectdata )
end


function AdvDupe.StartPaste( Player, inEntityList, inConstraintList, HeadEntityIdx, HitPos, HoldAngle, NumOfEnts, NumOfConst, PasteFrozen, PastewoConst, CallOnPasteFin, DontRemoveThinger, Thinger, FromPaster )
	hook.Add("Think", "AdvDupe_Think", AdvDupeThink)
	if ( FromPaster ) and ( NumOfEnts + NumOfConst > PasterInstantPasteThreshold ) then
		local CreatedEntities, CreatedConstraints = {},{}
		AdvDupe.NormPaste( Player, inEntityList, inConstraintList, HeadEntityIdx, HitPos, HoldAngle, Thinger, PasteFrozen, PastewoConst, CreatedEntities, CreatedConstraints )
		CallOnPasteFin( Thinger, CreatedEntities, CreatedConstraints )
	elseif ( NumOfEnts + NumOfConst > UseTimedPasteThreshold) then
		Msg("===adding new timed paste===\n")
		AdvDupe.OverTimePasteStart( Player, inEntityList, inConstraintList, HeadEntityIdx, HitPos, HoldAngle, NumOfEnts, NumOfConst, PasteFrozen, PastewoConst, CallOnPasteFin, DontRemoveThinger, Thinger )
	else
		Msg("===adding new delayed paste===\n")
		AdvDupe.AddDelayedPaste( Player, inEntityList, inConstraintList, HeadEntityIdx, HitPos, HoldAngle, false, PasteFrozen, PastewoConst, CallOnPasteFin, DontRemoveThinger, Thinger )
	end
end

function AdvDupe.AddDelayedPaste( Player, EntityList, ConstraintList, HeadEntityIdx, HitPos, HoldAngle, HideThinger, PasteFrozen, PastewoConst, CallOnPasteFin, DontRemoveThinger, Thinger )
	T					= {}
	T.Player			= Player
	T.EntityList		= EntityList
	T.ConstraintList	= ConstraintList
	T.HeadEntityIdx		= HeadEntityIdx
	T.HitPos			= HitPos
	T.HoldAngle			= HoldAngle
	if ( Thinger ) and ( Thinger:IsValid() ) then
		T.Shooting_Ent		= Thinger
		T.DontRemoveThinger = DontRemoveThinger
	else
		T.Shooting_Ent		= MakeThinger(Player, HideThinger)
	end
	T.NormPaste			= true
	T.Delay				= CurTime() + .2
	T.PasteFrozen		= PasteFrozen
	T.PastewoConst		= PastewoConst
	T.CallOnPasteFin	= CallOnPasteFin
	T.CreatedEntities	= {}
	T.CreatedEntities.EntityList = EntityList
	T.CreatedConstraints = {}
	table.insert(TimedPasteData, T)
end

function AdvDupe.NormPasteFromTable( PasteData )
	AdvDupe.NormPaste( PasteData.Player, PasteData.EntityList, PasteData.ConstraintList, 
	PasteData.HeadEntityIdx, PasteData.HitPos, PasteData.HoldAngle, PasteData.Shooting_Ent,
	PasteData.PasteFrozen, PasteData.PastewoConst, PasteData.CreatedEntities, PasteData.CreatedConstraints)
end

function AdvDupe.NormPaste( Player, EntityList, ConstraintList, HeadEntityIdx, Offset, HoldAngle, Shooting_Ent, PasteFrozen, PastewoConst, CreatedEntities, CreatedConstraints )
	AdvDupe.Paste( Player, EntityList, ConstraintList, HeadEntityIdx, Offset, HoldAngle, Shooting_Ent, PastewoConst, CreatedEntities, CreatedConstraints )
	CreatedEntities.EntityList = nil
	undo.Create( "Duplicator" )
		for EntID, Ent in pairs( CreatedEntities ) do
			undo.AddEntity( Ent )
			if ( PasteFrozen or PastewoConst ) then --and (Ent:GetPhysicsObject():IsValid()) then
				AdvDupe.FreezeEntity( Player, Ent, true )
			end
			
			if ( !PastewoConst ) then
				AdvDupe.ApplyParenting( Ent, EntID, EntityList, CreatedEntities )
			end
			-- Resets the positions of all the entities in the table
			EntTable = EntityList[ EntID ]
			EntTable.Pos = EntTable.LocalPos * 1
			EntTable.Angle = EntTable.LocalAngle * 1
			if ( EntTable.PhysicsObjects ) then
				for Num, Object in pairs( EntTable.PhysicsObjects ) do
					Object.Pos = Object.LocalPos * 1
					Object.Angle = Object.LocalAngle * 1
				end
			end
		end
		undo.SetPlayer( Player )
	undo.Finish()
end

function AdvDupe.Paste( Player, EntityList, ConstraintList, HeadEntityIdx, Offset, HoldAngle, Shooting_Ent, PastewoConst, CreatedEntities, CreatedConstraints )
	for EntID, EntTable in pairs( EntityList ) do
		CreatedEntities[ EntID ] = AdvDupe.PasteEntity( Player, EntTable, EntID, Offset, HoldAngle )
	end
	for EntID, Ent in pairs( CreatedEntities ) do	
		local NoFail, Result = pcall( AdvDupe.AfterPasteApply, Player, Ent, CreatedEntities )
		if ( !NoFail ) then
		end
	end
	if ( !PastewoConst ) and ( ConstraintList ) then
		for k, Constraint in pairs( ConstraintList ) do
			if ( Constraint.Type and Constraint.Type != "" ) then
				local Entity = AdvDupe.CreateConstraintFromTable( Player, Constraint, CreatedEntities, Offset, HoldAngle )
				if ( Entity && Entity:IsValid() ) then
					table.insert( CreatedConstraints, Entity )
				else
				end
			end
			
		end
	end
end

function AdvDupe.OverTimePasteStart( Player, inEntityList, inConstraintList, HeadEntityIdx, HitPos, HoldAngle, NumOfEnts, NumOfConst, PasteFrozen, PastewoConst, CallOnPasteFin, DontRemoveThinger, Thinger )
	local EntityList = {}
	local EntIDList = {}
	EntIDList[1] = HeadEntityIdx
	for EntID, EntTable in pairs( inEntityList ) do
		EntityList[EntID] = inEntityList[EntID]
		if ( EntID != HeadEntityIdx ) then
			table.insert( EntIDList, EntID )
		end
	end
	local ConstraintList = {}
	for ConstID, ConstTable in pairs( inConstraintList ) do
		table.insert(ConstraintList, ConstTable)
	end
	T						= {}
	T.Player				= Player
	T.EntityList			= EntityList
	T.ConstraintList		= ConstraintList
	T.HeadEntityIdx			= HeadEntityIdx
	T.CallsInRun			= 0
	T.Stage					= 1
	T.LastID				= 1
	T.EntIDList				= EntIDList
	T.CreatedEntities		= {}
	T.CreatedEntities.EntityList = EntityList
	T.CreatedConstraints	= {}
	T.HitPos				= HitPos
	T.HoldAngle				= HoldAngle
	if ( Thinger ) and ( Thinger:IsValid() ) then
		T.Shooting_Ent		= Thinger
		T.DontRemoveThinger = DontRemoveThinger
	else
		T.Shooting_Ent		= MakeThinger(Player)
	end
	T.CallOnPasteFin	= CallOnPasteFin
	T.Delay					= CurTime() + 0.2
	if ( PastewoConst ) then --guess how many ticks it will require so the progress bar looks right
		T.TotalTicks = math.ceil(NumOfEnts / PasteEntsPerTick) + math.ceil(NumOfEnts / PostEntityPastePerTick) + 5
	else
		T.TotalTicks = math.ceil(NumOfEnts / PasteEntsPerTick) + math.ceil(NumOfEnts / PostEntityPastePerTick) + math.ceil(NumOfConst / PasteConstsPerTick) + 5
	end
	T.PasteFrozen			= PasteFrozen
	T.PastewoConst			= PastewoConst
	table.insert(TimedPasteData, T)
end

function AdvDupe.OverTimePasteProcessFromTable()
	AdvDupe.OverTimePasteProcess(
	TimedPasteData[TimedPasteDataCurrent].Player, 
	TimedPasteData[TimedPasteDataCurrent].EntityList, 
	TimedPasteData[TimedPasteDataCurrent].ConstraintList, 
	TimedPasteData[TimedPasteDataCurrent].HeadEntityIdx, 
	TimedPasteData[TimedPasteDataCurrent].Stage, 
	TimedPasteData[TimedPasteDataCurrent].LastID, 
	TimedPasteData[TimedPasteDataCurrent].EntIDList, 
	TimedPasteData[TimedPasteDataCurrent].CreatedEntities, 
	TimedPasteData[TimedPasteDataCurrent].CreatedConstraints, 
	TimedPasteData[TimedPasteDataCurrent].Shooting_Ent,
	TimedPasteDataCurrent,
	TimedPasteData[TimedPasteDataCurrent].HitPos,
	TimedPasteData[TimedPasteDataCurrent].HoldAngle,
	TimedPasteData[TimedPasteDataCurrent].PasteFrozen,
	TimedPasteData[TimedPasteDataCurrent].PastewoConst )
end

function AdvDupe.OverTimePasteProcess( Player, EntityList, ConstraintList, HeadEntityIdx, Stage, LastID, EntIDList, CreatedEntities, CreatedConstraints, Shooting_Ent, DataNum, Offset, HoldAngle, PasteFrozen, PastewoConst )
	if Stage == 1 then
		if (DoPasteFX) then Shooting_Ent:EmitSound( "Airboat.FireGunRevDown" ) end
		for i = 1,PasteEntsPerTick do
			if EntIDList[ LastID ] then
				local EntID		= EntIDList[ LastID ]
				local EntTable	= EntityList[ EntID ]
				CreatedEntities[ EntID ] = AdvDupe.PasteEntity( Player, EntTable, EntID, Offset, HoldAngle )
				if ( CreatedEntities[ EntID ] ) then
					if (CreatedEntities[ EntID ]:GetPhysicsObject():IsValid()) then
						CreatedEntities[ EntID ]:GetPhysicsObject():EnableMotion(false)
					end
					CreatedEntities[ EntID ]:SetNotSolid(true)
					CreatedEntities[ EntID ]:SetParent( Shooting_Ent )
					if (DoPasteFX) and (math.random(5) > 3) then
						TingerFX( Shooting_Ent, CreatedEntities[ EntID ]:GetPos() )
					end
				end
				LastID = LastID + 1
			else
				LastID = 1
				Stage = 2
				break
			end
		end
	elseif Stage == 2 then
		for i = 1,PostEntityPastePerTick do
			if EntIDList[ LastID ] then
				local EntID		= EntIDList[ LastID ]
				local Ent		= CreatedEntities[ EntID ]
				if (Ent != nil) then
					local NoFail, Result = pcall( AdvDupe.AfterPasteApply, Player, Ent, CreatedEntities )
					if ( !NoFail ) then
					end
				end
				LastID = LastID + 1
			else
				LastID = 1
				Stage = 3
				break
			end
		end
	elseif Stage == 3 then
		if ( PastewoConst ) then
			TimedPasteData[DataNum].Stage  = 4
			return
		end
		if (DoPasteFX) then Shooting_Ent:EmitSound( "Airboat.FireGunRevDown" ) end
		for i = 1,PasteConstsPerTick do
			if ( ConstraintList and ConstraintList[ LastID ] ) then
				local Constraint = ConstraintList[ LastID ]
				if ( Constraint.Type and Constraint.Type != "" ) then
					local Entity = AdvDupe.CreateConstraintFromTable( Player, Constraint, CreatedEntities, Offset, HoldAngle )
					if ( Entity ) and ( Entity:IsValid() ) then
						table.insert( CreatedConstraints, Entity )
						if (DoPasteFX) and (math.random(5) > 3) then
							TingerFX( Shooting_Ent, CreatedEntities[ Constraint.Entity[1].Index ]:GetPos() )
						end
					else
						Entity = nil
					end
				end
				LastID = LastID + 1
			else
				LastID = 1
				Stage = 4
				break
			end
		end
	elseif Stage == 4 then
		CreatedEntities.EntityList = nil
		undo.Create( "Duplicator" )
			for EntID, Ent in pairs( CreatedEntities ) do
				if (Ent:IsValid()) then
					undo.AddEntity( Ent )
					EntTable = EntityList[ EntID ]
					Ent:SetNotSolid(false)
					Ent:SetParent()
					for Bone = 0, Ent:GetPhysicsObjectCount() do
						local Phys = Ent:GetPhysicsObjectNum( Bone )
						if ( Phys and Phys:IsValid() ) then
							if ( PasteFrozen or PastewoConst ) or ( EntTable.PhysicsObjects[0].Frozen ) then
								if ( !(Ent.EntityMods and Ent.EntityMods.Freeze_o_Matic_SuperFreeze and Ent.EntityMods.Freeze_o_Matic_SuperFreeze.NoPickUp == true) ) then
									Player:AddFrozenPhysicsObject( Ent, Phys )
								end
							else
								Phys:EnableMotion(true)
							end
						end
					end
					if ( Ent.RDbeamlibDrawer ) then
						Ent.RDbeamlibDrawer:SetParent( Ent )
					end
					if ( !PastewoConst ) then
						AdvDupe.ApplyParenting( Ent, EntID, EntityList, CreatedEntities )
					end
					EntTable.Pos = EntTable.LocalPos * 1
					EntTable.Angle = EntTable.LocalAngle * 1
					if ( EntTable.PhysicsObjects ) then
						for Num, Object in pairs( EntTable.PhysicsObjects ) do
							Object.Pos = Object.LocalPos * 1
							Object.Angle = Object.LocalAngle * 1
						end
					end
				else
					Ent = nil
				end
			end
			undo.SetPlayer( Player )
		undo.Finish()
		Stage = 5 --done!
	end
	TimedPasteData[DataNum].Stage  = Stage
	if Stage < 5 then
		TimedPasteData[DataNum].LastID = LastID
	end
end

function AdvDupe.FinishPasting( TimedPasteData,TimedPasteDataCurrent )
	if ( !TimedPasteData[TimedPasteDataCurrent].DontRemoveThinger ) then
		if ( TimedPasteData[TimedPasteDataCurrent].Shooting_Ent.Entity ) then
			TimedPasteData[TimedPasteDataCurrent].Shooting_Ent.Entity:Remove()
		end
		AdvDupe.HideGhost( TimedPasteData[TimedPasteDataCurrent].Player, false ) --unhide ghost now
		AdvDupe.SetPasting( TimedPasteData[TimedPasteDataCurrent].Player, false ) --allow the player to paste again
		AdvDupe.SetPercent(TimedPasteData[TimedPasteDataCurrent].Player, 100)
		timer.Simple(.1, AdvDupe.SetPercent, TimedPasteData[TimedPasteDataCurrent].Player, -1)
	end
	if ( TimedPasteData[TimedPasteDataCurrent].CallOnPasteFin ) then
		TimedPasteData[TimedPasteDataCurrent].CallOnPasteFin(
			TimedPasteData[TimedPasteDataCurrent].Shooting_Ent,
			TimedPasteData[TimedPasteDataCurrent].CreatedEntities, 
			TimedPasteData[TimedPasteDataCurrent].CreatedConstraints
		)
	end
	table.remove(TimedPasteData,TimedPasteDataCurrent)
end

function AdvDupe.PasteEntity( Player, EntTable, EntID, Offset, HoldAngle )
	if ( not AdvDupe.CheckOkEnt( Player, EntTable ) ) then
		return
	end
	local Ent = AdvDupe.CreateEntityFromTable( Player, EntTable, EntID, Offset, HoldAngle )
	if ( Ent and Ent:IsValid() ) then
		Player:AddCleanup( "duplicates", Ent )
		Ent.BoneMods = table.Copy( EntTable.BoneMods )
		Ent.EntityMods = table.Copy( EntTable.EntityMods )
		Ent.PhysicsObjects = table.Copy( EntTable.PhysicsObjects )
		local Success, Result = pcall( duplicator.ApplyEntityModifiers, Player, Ent )
		if ( !Success ) then
		end
		local Success, Result = pcall( duplicator.ApplyBoneModifiers, Player, Ent )
		if ( !Success ) then
		end
		return Ent
	else
		if (Ent and Ent:IsValid()) then Ent:Remove() end
		return
	end
end

function AdvDupe.GenericDuplicatorFunction( Player, data, ID )
	if (!data) or (!data.Class) then return false end
	local Entity = ents.Create( data.Class )
	if (!Entity:IsValid()) then
		Entity = ents.Create( "prop_physics" )
		Entity:SetCollisionGroup( COLLISION_GROUP_WORLD )
	end
	duplicator.DoGeneric( Entity, data )
	Entity:Spawn()
	Entity:Activate()
	duplicator.DoGenericPhysics( Entity, Player, data )
	table.Add( Entity:GetTable(), data )
	SpawnedProp( Player, _, Entity ) 
	return Entity
end

function AdvDupe.CreateEntityFromTable( Player, EntTable, ID, Offset, HoldAngle )
	local EntityClass = duplicator.FindEntityClass( EntTable.Class )
	local NewPos, NewAngle = LocalToWorld( EntTable.LocalPos, EntTable.LocalAngle, Offset, HoldAngle )
	EntTable.Pos = NewPos
	EntTable.Angle = NewAngle
	if ( EntTable.PhysicsObjects ) then
		for Num, Object in pairs( EntTable.PhysicsObjects ) do
			local NewPos, NewAngle = LocalToWorld( Object.LocalPos, Object.LocalAngle, Offset, HoldAngle )
			Object.Pos = NewPos
			Object.Angle = NewAngle
		end
	end
	if (!EntityClass) then
		return AdvDupe.GenericDuplicatorFunction( Player, EntTable, ID )
	end
	if (!EntTable.arglist) then
		EntTable.arglist = {}
		for iNumber, Key in pairs( EntityClass.Args ) do
			local Arg = nil
			if ( Key == "pos" || Key == "position" ) then Key = "Pos" end
			if ( Key == "ang" || Key == "Ang" || Key == "angle" ) then Key = "Angle" end
			if ( Key == "model" ) then Key = "Model" end
			Arg = EntTable[ Key ]
			if ( Key == "Data" ) then Arg = EntTable end
			if ( Arg == nil ) then Arg = false end
			EntTable.arglist[ iNumber ] = Arg
		end
	else	
		local fpos, fang = false, false --found ang and pos
		for iNumber, Key in pairs( EntityClass.Args ) do
			if ( Key == "pos" || Key == "position" || Key == "Pos") then
				EntTable.arglist[ iNumber ] = EntTable.Pos
				fpos = true
			end
			if ( Key == "ang" || Key == "Ang" || Key == "angle" || Key == "Angle" ) then
				EntTable.arglist[ iNumber ] = EntTable.Angle
				fang = true
			end
			if (fpos and fang) then break end
		end
	end
	local NoFail, Result
	if ( EntTable.Class == "prop_physics" ) then
		NoFail, Result = pcall( AdvDupe.MakeProp, Player, unpack(EntTable.arglist) )
	else
		NoFail, Result = pcall( EntityClass.Func, Player, unpack(EntTable.arglist) )
	end
	if ( !NoFail ) then
		AdvDupe.SendClientError( Player, "Failed to make \""..(EntTable.Class or "NIL").."\"" )
		return
	else
		return Result
	end
end

function AdvDupe.CreateConstraintFromTable( Player, Constraint, EntityList, Offset, HoldAngle )
	if ( !Constraint ) then return end
	local Factory = duplicator.ConstraintType[ Constraint.Type ]
	if ( !Factory ) then return end
	local Args = {}
	for k, Key in pairs( Factory.Args ) do
		local Val = Constraint[ Key ]
		if ( Key == "pl" ) then Val = Player end
		for i=1, 6 do 
			if ( Constraint.Entity[ i ] ) then
				if ( Key == "Ent"..i ) or ( Key == "Ent" ) then						
					if ( Constraint.Entity[ i ].World ) then
						Val = GetWorldEntity()
					else
						Val = EntityList[ Constraint.Entity[ i ].Index ] 
						if (!Val) or (!Val:IsValid()) then
							return
						end
					end
				end
				if ( Key == "Bone"..i ) or ( Key == "Bone" ) then Val = Constraint.Entity[ i ].Bone end
				if ( Key == "LPos"..i ) then
					if (Constraint.Entity[ i ].World && Constraint.Entity[ i ].LPos) then
						local NewPos, NewAngle = LocalToWorld( Constraint.Entity[ i ].LPos, Angle(0,0,0), Offset, HoldAngle )
						Val = NewPos
					else
						Val = Constraint.Entity[ i ].LPos
					end
				end
				if ( Key == "WPos"..i ) then Val = Constraint.Entity[ i ].WPos end
				if ( Key == "Length"..i ) then Val = Constraint.Entity[ i ].Length end
			end
		end
		if ( Val == nil ) then Val = false end
		table.insert( Args, Val )
	end
	if ( DebugWeldsByDrawingThem) and ( Constraint.Type == "Weld" ) then
		RDbeamlib.MakeSimpleBeam(
			EntityList[ Constraint.Entity[ 1 ].Index ], Vector(0,0,0), 
			EntityList[ Constraint.Entity[ 2 ].Index ], Vector(0,0,0), 
			"cable/cable2", Color(255,0,0,255), 1, true
		)
	end
	local NoFail, Result = pcall( Factory.Func, unpack(Args) )
	if ( !NoFail ) then
		AdvDupe.SendClientError( Player, "Failed to make \""..(Constraint.Type or "NIL").."\"" )
		return
	else
		if ( Constraint.Type == "Elastic" ) or ( Constraint.length ) then
			Result:Fire("SetSpringLength", Constraint.length, 0)
			Result.length = Constraint.length
		end
		return Result
	end
	
end

function AdvDupe.MakeProp( Player, Pos, Ang, Model, PhysicsObjects, Data )
	Data.Pos = Pos
	Data.Angle = Ang
	Data.Model = Model
	local Prop = ents.Create( "prop_physics" )
	duplicator.DoGeneric( Prop, Data )
	Prop:Spawn()
	Prop:Activate()
	duplicator.DoGenericPhysics( Prop, Player, Data )
	duplicator.DoFlex( Prop, Data.Flex, Data.FlexScale )
	gamemode.Call( "PlayerSpawnedProp", Player, Model, Prop )
	
	return Prop
end

function AdvDupe.AfterPasteApply( Player, Ent, CreatedEntities )
	if ( Ent.PostEntityPaste ) then
		Ent:PostEntityPaste( Player, Ent, CreatedEntities )
	end
	--clean up
	if (Ent.EntityMods) then
		if (Ent.EntityMods.RDDupeInfo) then -- fix: RDDupeInfo leak 
			Ent.EntityMods.RDDupeInfo = nil
		end
		if (Ent.EntityMods.WireDupeInfo) then 
			Ent.EntityMods.WireDupeInfo = nil
		end
	end
end

function AdvDupe.ApplyParenting( Ent, EntID, EntityList, CreatedEntities )
	if ( EntityList[ EntID ].SavedParentIdx ) then
		local Ent2 = CreatedEntities[ EntityList[ EntID ].SavedParentIdx ]
		if ( Ent2 ) and ( Ent2:IsValid() ) then
		if ( Ent != Ent2 ) then
			Ent:SetParent()
			if ( Ent == Ent2:GetParent() ) then
				Ent2:SetParent()
			end
			Ent:SetParent( Ent2 )
		end
		end
	end
end

function AdvDupe.FreezeEntity( pl, Ent, AddToFrozenList )
	for Bone = 0, Ent:GetPhysicsObjectCount() do
		local Phys = Ent:GetPhysicsObjectNum( Bone )
		if ( Phys and Phys:IsValid() ) then
			Phys:EnableMotion( false )
			if ( AddToFrozenList ) then
				pl:AddFrozenPhysicsObject( Ent, Phys )
			end
		end
	end
end

local CheckFunctions = {}
function AdvDupe.CheckOkEnt( Player, EntTable )
	EntTable.Class = EntTable.Class or ""
	for HookName, TheHook in pairs (CheckFunctions) do
		local Success, Result = pcall( TheHook.Func, Player, EntTable.Class, EntTable )
		if ( !Success ) then
			Error("AdvDupeERROR: Entity check hook \""..HookName.."\" failed, removing.\nHook Error: \""..tostring(Result).."\"\n")
			local OnFailCallBack = TheHook.OnFailCallBack
			CheckFunctions[ HookName ] = nil
			if ( OnFailCallBack ) then
				Msg("OnFailCallBack\n")
				local Success, Result = pcall( OnFailCallBack, HookName )
				if ( !Success ) then
					Error("AdvDupeERROR: WTF! \""..HookName.."\" OnFailCallBack failed too! Tell who ever make that hook that they're doing it wrong. Error: \""..tostring(Result).."\"\n")
				end
			end
		elseif ( Result == false ) then
			return false
		end
	end
	local test = scripted_ents.GetStored( EntTable.Class )
	if ( Player:IsAdmin( ) or Player:IsSuperAdmin() or SinglePlayer() or !DontAllowPlayersAdminOnlyEnts ) then
		return true
	elseif ( test and test.t and test.t.AdminSpawnable and !test.t.Spawnable ) then
		AdvDupe.SendClientError(Player, "Sorry, you can't cheat like that")
		Msg("AdvDupeERROR: "..tostring(Player).." tried to paste admin only prop "..(EntTable.Class or "NIL").." Ent: "..EntID.."\n")
		return false
	else
		return true
	end
end

function AdvDupe.AdminSettings.AddEntCheckHook( HookName, Func, OnFailCallBack )
	CheckFunctions[ HookName ] = {}
	CheckFunctions[ HookName ].Func = Func
	CheckFunctions[ HookName ].OnFailCallBack = OnFailCallBack
end
function AdvDupe.AdminSettings.RemoveEntCheckHook( HookName )
	if ( CheckFunctions[ HookName ] ) then
		CheckFunctions[ HookName ] = nil
		return true
	end
end

if (!SinglePlayer()) then
	local function NoItems(Player, Class, EntTable)
		if ( Player:IsAdmin( ) or Player:IsSuperAdmin() ) then return true end
		if string.find(Class, "^weapon_.*")
		or string.find(Class, "^item_.*")
		or string.find(Class, "^npc_.*") then
			Msg("AdvDupe: disalowing "..tostring(Player).." pasting item "..Class.." (NoItems Rule)\n")
			return false
		else
			return true
		end
	end
	local function AddNoItems()
		AdvDupe.AdminSettings.AddEntCheckHook("AdvDupe_NoItems", NoItems, AddNoItems)
	end
	AddNoItems()
	local function DisallowedClassesCheck(Player, ClassName, EntTable)
		if DisallowedClasses[ClassName] then
			if (DisallowedClasses[ClassName] == 2) then return false
			elseif ( DisallowedClasses[ClassName] == 1 and !Player:IsAdmin( ) and !Player:IsSuperAdmin() ) then return false end
		end
		return true
	end
	local function AddDisallowedClassesCheck()
		AdvDupe.AdminSettings.AddEntCheckHook("AdvDupe_NoItems", DisallowedClassesCheck, AddDisallowedClassesCheck)
	end
	AddDisallowedClassesCheck()
end

function AdvDupe.OldPaste( ply, Ents, Constraints, DupeInfo, DORInfo, HeadEntityID, offset )
	local constIDtable, entIDtable, CreatedConstraints, CreatedEnts = {}, {}, {}, {}
	local HeadEntity = nil
	Msg("\n=================--DoingLegacyPaste--=================\n")
	if (!Ents) then return false end
	for entID, EntTable in pairs( Ents ) do
		local Ent = nil
		local EntClass = EntTable.Class
		local EntType = duplicator.EntityClasses[EntClass]
		if EntClass and EntType then
			local Args = AdvDupe.PasteGetEntArgs( ply, EntTable, offset )
			if EntClass == "prop_physics" then
				Ent = AdvDupe.OldMakeProp( ply, unpack(Args) )
			elseif EntClass == "gmod_wheel" then
				Ent = AdvDupe.OldMakeWheel( ply, unpack(Args) )
			else
				Ent = EntType.Func( ply, unpack(Args) )
			end
		elseif (EntClass) then
			Msg("Duplicator paste: Unknown ent class " .. (EntClass or "NIL") .. "\n")
		end
		if Ent and Ent:IsValid() then
			entIDtable[entID] = Ent
			table.insert(CreatedEnts,Ent)
			table.Add( Ent:GetTable(), EntTable )
			AdvDupe.PasteApplyEntMods( ply, Ent, EntTable )
		end
		if ( entID == HeadEntityID ) then
			HeadEntity = Ent
		end
	end
	
	for _, Constraint in pairs(Constraints) do
		local ConstraintType = duplicator.ConstraintType[Constraint.Type]
		if Constraint.Type and ConstraintType then
			local Args, DoConstraint = AdvDupe.PasteGetConstraintArgs( ply, Constraint, entIDtable, offset )
			if DoConstraint then
				local const = ConstraintType.Func(unpack(Args))
				table.insert(CreatedConstraints,const)
			end
		elseif (Constraint.Type) then
			Msg("Duplicator paste: Unknown constraint " .. (Constraint.Type or "NIL") .. "\n")
		end
	end
	AdvDupe.PasteApplyDupeInfo( ply, DupeInfo, entIDtable )
	AdvDupe.PasteApplyDORInfo( DORInfo, function(id) return entIDtable[id] end )
	return CreatedEnts, CreatedConstraints
end

function AdvDupe.PasteGetEntArgs( ply, EntTable, offset )
	local EntArgs, Args, BoneArgs, nBone = {}, {}, nil, nil
	if EntTable.Class == "prop_physics"  then
		EntArgs = {"Pos", "Ang", "Model", "Vel", "aVel", "frozen"}
	elseif EntTable.Class == "gmod_wheel"  then
		EntArgs = {"Pos", "Ang", "model", "Vel", "aVel", "frozen", "key_f", "key_r"}
	else
		EntArgs = duplicator.EntityClasses[EntTable.Class].Args
	end
	for n,Key in pairs(EntArgs) do
		if type(Key) == "table" then
			BoneArgs = Key
			nBone	 = n
		else
			local Arg = EntTable[Key]
			key = string.lower(Key)
			if		key == "ang"	or key == "angle"			then Arg = Arg or Vector(0,0,0)
			elseif	key == "pos"	or key == "position"		then Arg = Arg + offset or Vector(0,0,0)
			elseif	key == "vel"	or key == "velocity"		then Arg = Arg or Vector(0,0,0)
			elseif	key == "avel"	or key == "anglevelocity"	then Arg = Arg or Vector(0,0,0)
			elseif	key == "pl" 	or key == "ply"				then Arg = ply 
			end
			Args[n] = Arg
		end
	end
	
	if EntTable.Bones and BoneArgs then
		local Arg = {}
		for Bone,Args in pairs(EntTable.Bones) do
			Arg[Bone] = {}
			for n, bKey in pairs( BoneArgs ) do
				local bArg = EntTable.Bones[Bone][bKey] or tostring(0)
				local bkey = string.lower(bKey)
				if	bkey == "ang"	or bkey == "angle"				then bArg = bArg or Vector(0,0,0)
				elseif	bkey == "pos"	or bkey == "position"		then bArg = bArg + offset or Vector(0,0,0)
				elseif	bkey == "vel"	or bkey == "velocity"		then bArg = bArg or Vector(0,0,0)
				elseif	bkey == "avel"	or bkey == "angvelocity"	then bArg = bArg or Vector(0,0,0)
				end
				Arg[Bone][n] = bArg
			end
		end
		Args[nBone] = Arg
	end
	return Args
end



function AdvDupe.PasteApplyEntMods( ply, Ent, EntTable )
	for ModifierType, Modifier in pairs(AdvDupe.OldEntityModifiers) do
		if EntTable[ModifierType] then
			Msg("Applying Mod Type: "..ModifierType.."\n")
			local args = {}
			for n,arg in pairs(Modifier.Args) do
				args[n] = EntTable[ModifierType][arg]
			end
			Modifier.Func( ply, Ent, unpack(args))
		end
	end
	if EntTable.Bones then
		for BoneID,Args in pairs(EntTable.Bones) do
			if Args["physprops"] then
				local Data = {}
				for n,arg in pairs({"motionb", "gravityb", "mass", "dragb", "drag", "buoyancy", "rotdamping", "speeddamping", "material"}) do
					Data[n] = Args["physprops"][arg]
				end
				local PhysObject = Ent:GetPhysicsObjectNum( BoneID )
				AdvDupe.OldSetPhysProp( Player, Ent, BoneID, PhysObject, Data )
			end
		end
	end
	
end

-- Get the args to make the constraints
function AdvDupe.PasteGetConstraintArgs( ply, Constraint, entIDtable, offset )
	local Args = {}
	local DoConstraint = true
	local ConstraintType = duplicator.ConstraintType[Constraint.Type]
	
	-- Get the args that we need from the ConstraintType table
	for n,key in pairs(ConstraintType.Args) do
		
		local Arg = Constraint[key]
		local len = string.len(key)
		
		-- DO SPECIAL CASES
		-- If key represents an entity, convert from an entID back to an ent
		if	string.find(key, "Ent")		and ( len == 3 or len == 4 ) then
			Arg = entIDtable[Arg]
			if !Arg or !Arg:IsValid() then DoConstraint = nil end
			
		-- If key represents an Local angle or vector, convert from string, back to a vector
		elseif	(string.find(key, "LPos")	and ( len == 4 or len == 5 ))
		or	(string.find(key, "Ang")	and ( len == 3 or len == 4 )) then 
			Arg = Arg or Vector(0,0,0)
			
		-- If key represents a World Vector or angle, convert from string, back to a vector
		elseif	(string.find(key, "WPos")	and ( len == 4 or len == 5 )) then
			Arg = Arg + offset or Vector(0,0,0)
			
		-- If key represents a ply, convert from steamid back to a ply
		elseif	key == "pl" or key == "ply" or key == "ply" then
			--Arg = ply.GetBySteamID(Arg)
			Arg = ply
			if not Arg:IsValid() then DoConstraint = nil end
		end
		
		Args[n] = Arg
	end
	
	return Args, DoConstraint
end

-- Apply DupeInfo for wire stuff
function AdvDupe.PasteApplyDupeInfo( ply, DupeInfoTable, entIDtable )
	if (!DupeInfoTable) then return end
	for id, infoTable in pairs(DupeInfoTable) do
		local ent = entIDtable[id]
		if (ent) and (ent:IsValid()) and (infoTable) and (ent.ApplyDupeInfo) then
			ent:ApplyDupeInfo( ply, ent, infoTable, function(id) return entIDtable[id] end )
		end
	end
end

-- Apply DORInfo for DeleteOnRemove
function AdvDupe.PasteApplyDORInfo( DORInfoTable, GetentID )
	
	for id, DORInfo in pairs(DORInfoTable) do
		local ent = GetentID(id)
		if (ent) and (ent:IsValid()) and (DORInfo) then
			--ent:SetDeleteOnRemoveInfo(DORInfo, function(id) return GetentID(id) end)
			
			for _,entindex in pairs(DORInfo) do
				local ent2 = GetentID(entindex)
				if (ent2 && ent2:IsValid() && ent2:EntIndex() > 0) then
					-- Add the entity
					
					ent:DeleteOnRemove(ent2)
				end
			end
			
		end
	end
	
end

--	Register camera entity class
--	fixes key not being saved (Conna)
local function CamRegister(Player, Pos, Ang, Key, Locked, Toggle, Vel, aVel, Frozen, Nocollide)
	if (!Key) then return end
	
	local Camera = ents.Create("gmod_cameraprop")
	Camera:SetAngles(Ang)
	Camera:SetPos(Pos)
	Camera:Spawn()
	Camera:SetKey(Key)
	Camera:SetPlayer(Player)
	Camera:SetLocked(Locked)
	Camera.toggle = Toggle
	Camera:SetTracking(NULL, Vector(0))
	SpawnedProp( Player, _, Entity ) 
	if (Toggle == 1) then
		numpad.OnDown(Player, Key, "Camera_Toggle", Camera)
	else
		numpad.OnDown(Player, Key, "Camera_On", Camera)
		numpad.OnUp(Player, Key, "Camera_Off", Camera)
	end
	
	if (Nocollide) then Camera:GetPhysicsObject():EnableCollisions(false) end
	
	-- Merge table
		local Table = {
		key			= Key,
		toggle 		= Toggle,
		locked      = Locked,
		pl			= Player,
		nocollide 	= nocollide
	}
	table.Merge(Camera:GetTable(), Table)
	
	-- remove any camera that has the same key defined for this player then add the new one
	local ID = Player:UniqueID()
		GAMEMODE.CameraList[ID] = GAMEMODE.CameraList[ID] or {}
	local List = GAMEMODE.CameraList[ID]
	if (List[Key] and List[Key] != NULL ) then
		local Entity = List[Key]
		Entity:Remove()
	end
	List[Key] = Camera
	return Camera
	
end
duplicator.RegisterEntityClass("gmod_cameraprop", CamRegister, "Pos", "Ang", "key", "locked", "toggle", "Vel", "aVel", "frozen", "nocollide")

function AdvDupe.ConvertEntityPositionsToWorld( EntTable, Offset, HoldAngle )
	for k, Ent in pairs( EntTable ) do
		local NewPos, NewAngle = LocalToWorld( Ent.LocalPos, Ent.LocalAngle, Offset, HoldAngle )
		Ent.Pos = NewPos
		Ent.Angle = NewAngle
		-- And for physics objects
		if ( Ent.PhysicsObjects ) then
			for Num, Object in pairs( Ent.PhysicsObjects ) do
				local NewPos, NewAngle = LocalToWorld( Object.LocalPos, Object.LocalAngle, Offset, HoldAngle )
				Object.Pos = NewPos
				Object.Angle = NewAngle
			end
		end
	end
end

-- Move the world positions
function AdvDupe.ConvertConstraintPositionsToWorld( Constraints, Offset, HoldAngle )
	if (!Constraints) then return end
	for k, Constraint in pairs( Constraints ) do
		if ( Constraint.Entity ) then
			for k, Entity in pairs( Constraint.Entity ) do
				if (Entity.World && Entity.LPos) then
					local NewPos, NewAngle = LocalToWorld( Entity.LPos, Angle(0,0,0), Offset, HoldAngle )
					Entity.LPosOld = Entity.LPos
					Entity.LPos = NewPos
				end
			end
		end
	end
end

-- Resets the positions of all the entities in the table
function AdvDupe.ResetPositions( EntTable, Constraints )

	for k, Ent in pairs( EntTable ) do
		Ent.Pos = Ent.LocalPos * 1
		Ent.Angle = Ent.LocalAngle * 1
		-- And for physics objects
		if ( Ent.PhysicsObjects ) then
			for Num, Object in pairs( Ent.PhysicsObjects ) do
				Object.Pos = Object.LocalPos * 1
				Object.Angle = Object.LocalAngle * 1
			end
		end
	end
end

-- Converts the positions from world positions to positions local to Offset
function AdvDupe.ConvertPositionsToLocal( EntTable, Constraints, Offset, HoldAngle )

	for k, Ent in pairs( EntTable ) do
		Ent.Pos = Ent.Pos - Offset
		Ent.LocalPos = Ent.Pos * 1
		Ent.LocalAngle = Ent.Angle * 1
		if ( Ent.PhysicsObjects ) then
			for Num, Object in pairs(Ent.PhysicsObjects) do
				Object.Pos = Object.Pos - Offset
				Object.LocalPos = Object.Pos * 1
				Object.LocalAngle = Object.Angle * 1
				Object.Pos = nil
				Object.Angle = nil
			end
		end
	end
	
	-- If the entity is constrained to the world we want to move the points to be
	-- relative to where we're clicking
	if (!Constraints) then return end
	for k, Constraint in pairs( Constraints ) do
		if ( Constraint.Entity ) then
			for k, Entity in pairs( Constraint.Entity ) do
				if (Entity.World && Entity.LPos) then
					Entity.LPos = Entity.LPos - Offset
				end
			end
		end
	end

end




Msg("==== Advanced Duplicator v."..AdvDupe.Version.." server module installed! ====\n")
